Skip to content

Commit 2f417a3

Browse files
authored
feat(bigquery): add support for allowing Javascript UDFs to indicate determinism (#3534)
BigQuery supports javascript UDFs, which allow users to define function bodies to be executed via javascrtipt. Allowing users to indicate determinism level of the UDF helps the query engine make more efficient choices around aspects like query caching. Fixes: #3533
1 parent 1961930 commit 2f417a3

File tree

3 files changed

+56
-7
lines changed

3 files changed

+56
-7
lines changed

Diff for: bigquery/integration_test.go

+19-4
Original file line numberDiff line numberDiff line change
@@ -2916,18 +2916,33 @@ func TestIntegration_RoutineJSUDF(t *testing.T) {
29162916
// Create a scalar UDF routine via API.
29172917
routineID := routineIDs.New()
29182918
routine := dataset.Routine(routineID)
2919-
err := routine.Create(ctx, &RoutineMetadata{
2919+
meta := &RoutineMetadata{
29202920
Language: "JAVASCRIPT", Type: "SCALAR_FUNCTION",
2921-
Description: "capitalizes using javascript",
2921+
Description: "capitalizes using javascript",
2922+
DeterminismLevel: Deterministic,
29222923
Arguments: []*RoutineArgument{
29232924
{Name: "instr", Kind: "FIXED_TYPE", DataType: &StandardSQLDataType{TypeKind: "STRING"}},
29242925
},
29252926
ReturnType: &StandardSQLDataType{TypeKind: "STRING"},
29262927
Body: "return instr.toUpperCase();",
2927-
})
2928-
if err != nil {
2928+
}
2929+
if err := routine.Create(ctx, meta); err != nil {
29292930
t.Fatalf("Create: %v", err)
29302931
}
2932+
2933+
newMeta := &RoutineMetadataToUpdate{
2934+
Language: meta.Language,
2935+
Body: meta.Body,
2936+
Arguments: meta.Arguments,
2937+
Description: meta.Description,
2938+
ReturnType: meta.ReturnType,
2939+
Type: meta.Type,
2940+
2941+
DeterminismLevel: NotDeterministic,
2942+
}
2943+
if _, err := routine.Update(ctx, newMeta, ""); err != nil {
2944+
t.Fatalf("Update: %v", err)
2945+
}
29312946
}
29322947

29332948
func TestIntegration_RoutineComplexTypes(t *testing.T) {

Diff for: bigquery/routine.go

+35-3
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,27 @@ func (r *Routine) Delete(ctx context.Context) (err error) {
129129
return req.Do()
130130
}
131131

132+
// RoutineDeterminism specifies the level of determinism that javascript User Defined Functions
133+
// exhibit.
134+
type RoutineDeterminism string
135+
136+
const (
137+
// Deterministic indicates that two calls with the same input to a UDF yield the same output.
138+
Deterministic RoutineDeterminism = "DETERMINISTIC"
139+
// NotDeterministic indicates that the output of the UDF is not guaranteed to yield the same
140+
// output each time for a given set of inputs.
141+
NotDeterministic RoutineDeterminism = "NOT_DETERMINISTIC"
142+
)
143+
132144
// RoutineMetadata represents details of a given BigQuery Routine.
133145
type RoutineMetadata struct {
134146
ETag string
135147
// Type indicates the type of routine, such as SCALAR_FUNCTION or PROCEDURE.
136-
Type string
137-
CreationTime time.Time
138-
Description string
148+
Type string
149+
CreationTime time.Time
150+
Description string
151+
// DeterminismLevel is only applicable to Javascript UDFs.
152+
DeterminismLevel RoutineDeterminism
139153
LastModifiedTime time.Time
140154
// Language of the routine, such as SQL or JAVASCRIPT.
141155
Language string
@@ -161,6 +175,7 @@ func (rm *RoutineMetadata) toBQ() (*bq.Routine, error) {
161175
return r, nil
162176
}
163177
r.Description = rm.Description
178+
r.DeterminismLevel = string(rm.DeterminismLevel)
164179
r.Language = rm.Language
165180
r.RoutineType = rm.Type
166181
r.DefinitionBody = rm.Body
@@ -280,6 +295,7 @@ func routineArgumentsToBQ(in []*RoutineArgument) ([]*bq.Argument, error) {
280295
type RoutineMetadataToUpdate struct {
281296
Arguments []*RoutineArgument
282297
Description optional.String
298+
DeterminismLevel optional.String
283299
Type optional.String
284300
Language optional.String
285301
Body optional.String
@@ -299,6 +315,21 @@ func (rm *RoutineMetadataToUpdate) toBQ() (*bq.Routine, error) {
299315
r.Description = optional.ToString(rm.Description)
300316
forceSend("Description")
301317
}
318+
if rm.DeterminismLevel != nil {
319+
processed := false
320+
// Allow either string or RoutineDeterminism, a type based on string.
321+
if x, ok := rm.DeterminismLevel.(RoutineDeterminism); ok {
322+
r.DeterminismLevel = string(x)
323+
processed = true
324+
}
325+
if x, ok := rm.DeterminismLevel.(string); ok {
326+
r.DeterminismLevel = x
327+
processed = true
328+
}
329+
if !processed {
330+
panic(fmt.Sprintf("DeterminismLevel should be either type string or RoutineDetermism in update, got %T", rm.DeterminismLevel))
331+
}
332+
}
302333
if rm.Arguments != nil {
303334
if len(rm.Arguments) == 0 {
304335
nullField("Arguments")
@@ -348,6 +379,7 @@ func bqToRoutineMetadata(r *bq.Routine) (*RoutineMetadata, error) {
348379
Type: r.RoutineType,
349380
CreationTime: unixMillisToTime(r.CreationTime),
350381
Description: r.Description,
382+
DeterminismLevel: RoutineDeterminism(r.DeterminismLevel),
351383
LastModifiedTime: unixMillisToTime(r.LastModifiedTime),
352384
Language: r.Language,
353385
ImportedLibraries: r.ImportedLibraries,

Diff for: bigquery/routine_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ func TestRoutineTypeConversions(t *testing.T) {
8080
DefinitionBody: "body",
8181
Description: "desc",
8282
Etag: "etag",
83+
DeterminismLevel: "DETERMINISTIC",
8384
RoutineType: "type",
8485
Language: "lang",
8586
ReturnType: &bq.StandardSqlDataType{TypeKind: "INT64"},
@@ -88,6 +89,7 @@ func TestRoutineTypeConversions(t *testing.T) {
8889
CreationTime: aTime,
8990
LastModifiedTime: aTime,
9091
Description: "desc",
92+
DeterminismLevel: Deterministic,
9193
Body: "body",
9294
ETag: "etag",
9395
Type: "type",

0 commit comments

Comments
 (0)