Skip to content

Commit

Permalink
feat(bigquery): add support for allowing Javascript UDFs to indicate …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
shollyman committed Jan 14, 2021
1 parent 1961930 commit 2f417a3
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 7 deletions.
23 changes: 19 additions & 4 deletions bigquery/integration_test.go
Expand Up @@ -2916,18 +2916,33 @@ func TestIntegration_RoutineJSUDF(t *testing.T) {
// Create a scalar UDF routine via API.
routineID := routineIDs.New()
routine := dataset.Routine(routineID)
err := routine.Create(ctx, &RoutineMetadata{
meta := &RoutineMetadata{
Language: "JAVASCRIPT", Type: "SCALAR_FUNCTION",
Description: "capitalizes using javascript",
Description: "capitalizes using javascript",
DeterminismLevel: Deterministic,
Arguments: []*RoutineArgument{
{Name: "instr", Kind: "FIXED_TYPE", DataType: &StandardSQLDataType{TypeKind: "STRING"}},
},
ReturnType: &StandardSQLDataType{TypeKind: "STRING"},
Body: "return instr.toUpperCase();",
})
if err != nil {
}
if err := routine.Create(ctx, meta); err != nil {
t.Fatalf("Create: %v", err)
}

newMeta := &RoutineMetadataToUpdate{
Language: meta.Language,
Body: meta.Body,
Arguments: meta.Arguments,
Description: meta.Description,
ReturnType: meta.ReturnType,
Type: meta.Type,

DeterminismLevel: NotDeterministic,
}
if _, err := routine.Update(ctx, newMeta, ""); err != nil {
t.Fatalf("Update: %v", err)
}
}

func TestIntegration_RoutineComplexTypes(t *testing.T) {
Expand Down
38 changes: 35 additions & 3 deletions bigquery/routine.go
Expand Up @@ -129,13 +129,27 @@ func (r *Routine) Delete(ctx context.Context) (err error) {
return req.Do()
}

// RoutineDeterminism specifies the level of determinism that javascript User Defined Functions
// exhibit.
type RoutineDeterminism string

const (
// Deterministic indicates that two calls with the same input to a UDF yield the same output.
Deterministic RoutineDeterminism = "DETERMINISTIC"
// NotDeterministic indicates that the output of the UDF is not guaranteed to yield the same
// output each time for a given set of inputs.
NotDeterministic RoutineDeterminism = "NOT_DETERMINISTIC"
)

// RoutineMetadata represents details of a given BigQuery Routine.
type RoutineMetadata struct {
ETag string
// Type indicates the type of routine, such as SCALAR_FUNCTION or PROCEDURE.
Type string
CreationTime time.Time
Description string
Type string
CreationTime time.Time
Description string
// DeterminismLevel is only applicable to Javascript UDFs.
DeterminismLevel RoutineDeterminism
LastModifiedTime time.Time
// Language of the routine, such as SQL or JAVASCRIPT.
Language string
Expand All @@ -161,6 +175,7 @@ func (rm *RoutineMetadata) toBQ() (*bq.Routine, error) {
return r, nil
}
r.Description = rm.Description
r.DeterminismLevel = string(rm.DeterminismLevel)
r.Language = rm.Language
r.RoutineType = rm.Type
r.DefinitionBody = rm.Body
Expand Down Expand Up @@ -280,6 +295,7 @@ func routineArgumentsToBQ(in []*RoutineArgument) ([]*bq.Argument, error) {
type RoutineMetadataToUpdate struct {
Arguments []*RoutineArgument
Description optional.String
DeterminismLevel optional.String
Type optional.String
Language optional.String
Body optional.String
Expand All @@ -299,6 +315,21 @@ func (rm *RoutineMetadataToUpdate) toBQ() (*bq.Routine, error) {
r.Description = optional.ToString(rm.Description)
forceSend("Description")
}
if rm.DeterminismLevel != nil {
processed := false
// Allow either string or RoutineDeterminism, a type based on string.
if x, ok := rm.DeterminismLevel.(RoutineDeterminism); ok {
r.DeterminismLevel = string(x)
processed = true
}
if x, ok := rm.DeterminismLevel.(string); ok {
r.DeterminismLevel = x
processed = true
}
if !processed {
panic(fmt.Sprintf("DeterminismLevel should be either type string or RoutineDetermism in update, got %T", rm.DeterminismLevel))
}
}
if rm.Arguments != nil {
if len(rm.Arguments) == 0 {
nullField("Arguments")
Expand Down Expand Up @@ -348,6 +379,7 @@ func bqToRoutineMetadata(r *bq.Routine) (*RoutineMetadata, error) {
Type: r.RoutineType,
CreationTime: unixMillisToTime(r.CreationTime),
Description: r.Description,
DeterminismLevel: RoutineDeterminism(r.DeterminismLevel),
LastModifiedTime: unixMillisToTime(r.LastModifiedTime),
Language: r.Language,
ImportedLibraries: r.ImportedLibraries,
Expand Down
2 changes: 2 additions & 0 deletions bigquery/routine_test.go
Expand Up @@ -80,6 +80,7 @@ func TestRoutineTypeConversions(t *testing.T) {
DefinitionBody: "body",
Description: "desc",
Etag: "etag",
DeterminismLevel: "DETERMINISTIC",
RoutineType: "type",
Language: "lang",
ReturnType: &bq.StandardSqlDataType{TypeKind: "INT64"},
Expand All @@ -88,6 +89,7 @@ func TestRoutineTypeConversions(t *testing.T) {
CreationTime: aTime,
LastModifiedTime: aTime,
Description: "desc",
DeterminismLevel: Deterministic,
Body: "body",
ETag: "etag",
Type: "type",
Expand Down

0 comments on commit 2f417a3

Please sign in to comment.