Repository: googleapis/google-cloud-go
Pull request #6464 (2022-08-02 22:11:14)

feat(bigquery): add preview support for default values


Patch:
diff --git a/bigquery/schema.go b/bigquery/schema.go
index aa9efe36e5..c35ac17f29 100644
--- a/bigquery/schema.go
+++ b/bigquery/schema.go
@@ -121,17 +121,38 @@ type FieldSchema struct {
 	//
 	// See the Precision field for additional guidance about valid values.
 	Scale int64
+
+	// DefaultValueExpression is used to specify the default value of a field
+	// using a SQL expression.  It can only be set for top level fields (columns).
+	//
+	// You can use struct or array expression to specify default value for the
+	// entire struct or array. The valid SQL expressions are:
+	//
+	// - Literals for all data types, including STRUCT and ARRAY.
+	// - The following functions:
+	//   - CURRENT_TIMESTAMP
+	//   - CURRENT_TIME
+	//   - CURRENT_DATE
+	//   - CURRENT_DATETIME
+	//   - GENERATE_UUID
+	//   - RAND
+	//   - SESSION_USER
+	//   - ST_GEOGPOINT
+	//   - Struct or array composed with the above allowed functions, for example:
+	//       [CURRENT_DATE(), DATE '2020-01-01']"
+	DefaultValueExpression string
 }
 
 func (fs *FieldSchema) toBQ() *bq.TableFieldSchema {
 	tfs := &bq.TableFieldSchema{
-		Description: fs.Description,
-		Name:        fs.Name,
-		Type:        string(fs.Type),
-		PolicyTags:  fs.PolicyTags.toBQ(),
-		MaxLength:   fs.MaxLength,
-		Precision:   fs.Precision,
-		Scale:       fs.Scale,
+		Description:            fs.Description,
+		Name:                   fs.Name,
+		Type:                   string(fs.Type),
+		PolicyTags:             fs.PolicyTags.toBQ(),
+		MaxLength:              fs.MaxLength,
+		Precision:              fs.Precision,
+		Scale:                  fs.Scale,
+		DefaultValueExpression: fs.DefaultValueExpression,
 	}
 
 	if fs.Repeated {
@@ -181,15 +202,16 @@ func (s Schema) toBQ() *bq.TableSchema {
 
 func bqToFieldSchema(tfs *bq.TableFieldSchema) *FieldSchema {
 	fs := &FieldSchema{
-		Description: tfs.Description,
-		Name:        tfs.Name,
-		Repeated:    tfs.Mode == "REPEATED",
-		Required:    tfs.Mode == "REQUIRED",
-		Type:        FieldType(tfs.Type),
-		PolicyTags:  bqToPolicyTagList(tfs.PolicyTags),
-		MaxLength:   tfs.MaxLength,
-		Precision:   tfs.Precision,
-		Scale:       tfs.Scale,
+		Description:            tfs.Description,
+		Name:                   tfs.Name,
+		Repeated:               tfs.Mode == "REPEATED",
+		Required:               tfs.Mode == "REQUIRED",
+		Type:                   FieldType(tfs.Type),
+		PolicyTags:             bqToPolicyTagList(tfs.PolicyTags),
+		MaxLength:              tfs.MaxLength,
+		Precision:              tfs.Precision,
+		Scale:                  tfs.Scale,
+		DefaultValueExpression: tfs.DefaultValueExpression,
 	}
 
 	for _, f := range tfs.Fields {


Test patch:
diff --git a/bigquery/schema_test.go b/bigquery/schema_test.go
index d69cbbd822..d3ca8f7fa7 100644
--- a/bigquery/schema_test.go
+++ b/bigquery/schema_test.go
@@ -320,6 +320,34 @@ func TestSchemaConversion(t *testing.T) {
 				},
 			},
 		},
+		{
+			// default values
+			bqSchema: &bq.TableSchema{
+				Fields: []*bq.TableFieldSchema{
+					{
+						Name:                   "foo",
+						Type:                   "STRING",
+						DefaultValueExpression: "I_LOVE_FOO",
+					},
+					{
+						Name:                   "bar",
+						Type:                   "TIMESTAMP",
+						DefaultValueExpression: "CURRENT_TIMESTAMP()",
+					},
+				}},
+			schema: Schema{
+				{
+					Name:                   "foo",
+					Type:                   StringFieldType,
+					DefaultValueExpression: "I_LOVE_FOO",
+				},
+				{
+					Name:                   "bar",
+					Type:                   TimestampFieldType,
+					DefaultValueExpression: "CURRENT_TIMESTAMP()",
+				},
+			},
+		},
 		{
 			// policy tags
 			bqSchema: &bq.TableSchema{
diff --git a/bigquery/table_integration_test.go b/bigquery/table_integration_test.go
index c6e06fd122..689c911ccc 100644
--- a/bigquery/table_integration_test.go
+++ b/bigquery/table_integration_test.go
@@ -78,7 +78,54 @@ func TestIntegration_TableCreateWithConstraints(t *testing.T) {
 	if diff := testutil.Diff(meta.Schema, schema); diff != "" {
 		t.Fatalf("got=-, want=+:\n%s", diff)
 	}
+}
+
+func TestIntegration_TableCreateWithDefaultValues(t *testing.T) {
+	if client == nil {
+		t.Skip("Integration tests skipped")
+	}
+	ctx := context.Background()
+	table := dataset.Table("defaultvalues")
+	schema := Schema{
+		{Name: "str_col", Type: StringFieldType, DefaultValueExpression: "'FOO'"},
+		{Name: "timestamp_col", Type: TimestampFieldType, DefaultValueExpression: "CURRENT_TIMESTAMP()"},
+	}
+	err := table.Create(ctx, &TableMetadata{
+		Schema:         schema,
+		ExpirationTime: testTableExpiration.Add(5 * time.Minute),
+	})
+	if err != nil {
+		t.Fatalf("table create error: %v", err)
+	}
+
+	meta, err := table.Metadata(ctx)
+	if err != nil {
+		t.Fatalf("couldn't get metadata: %v", err)
+	}
+
+	if diff := testutil.Diff(meta.Schema, schema); diff != "" {
+		t.Fatalf("got=-, want=+:\n%s", diff)
+	}
 
+	// SQL creation
+	id, _ := table.Identifier(StandardSQLID)
+	sql := fmt.Sprintf(`
+	    CREATE OR REPLACE TABLE %s (
+			str_col STRING DEFAULT 'FOO',
+			timestamp_col TIMESTAMP DEFAULT CURRENT_TIMESTAMP(),
+		)`, id)
+	_, _, err = runQuerySQL(ctx, sql)
+	if err != nil {
+		t.Fatal(err)
+	}
+	meta, err = table.Metadata(ctx)
+	if err != nil {
+		t.Fatalf("couldn't get metadata after sql: %v", err)
+	}
+
+	if diff := testutil.Diff(meta.Schema, schema); diff != "" {
+		t.Fatalf("sql create: got=-, want=+:\n%s", diff)
+	}
 }
 
 func TestIntegration_TableCreateView(t *testing.T) {