diff --git a/spanner/spannertest/db.go b/spanner/spannertest/db.go index 920a44af4b2..f922e77af6d 100644 --- a/spanner/spannertest/db.go +++ b/spanner/spannertest/db.go @@ -700,12 +700,20 @@ func (t *table) addColumn(cd spansql.ColumnDef, newTable bool) *status.Status { // TODO: what happens in this case? return status.Newf(codes.Unimplemented, "can't add NOT NULL columns to non-empty tables yet") } - if cd.Generated != nil { - // TODO: should backfill the data to maintain behaviour with real spanner - return status.Newf(codes.Unimplemented, "can't add generated columns to non-empty tables yet") - } for i := range t.rows { - t.rows[i] = append(t.rows[i], nil) + if cd.Generated != nil { + ec := evalContext{ + cols: t.cols, + row: t.rows[i], + } + val, err := ec.evalExpr(cd.Generated) + if err != nil { + return status.Newf(codes.InvalidArgument, "could not backfill values for generated column: %v", err) + } + t.rows[i] = append(t.rows[i], val) + } else { + t.rows[i] = append(t.rows[i], nil) + } } } diff --git a/spanner/spannertest/db_test.go b/spanner/spannertest/db_test.go index 9e8da729162..dbc90e12ce2 100644 --- a/spanner/spannertest/db_test.go +++ b/spanner/spannertest/db_test.go @@ -441,8 +441,8 @@ func TestGeneratedColumn(t *testing.T) { if err != nil { t.Fatalf("%s: Bad DDL", err) } - if st := db.ApplyDDL(ddl.List[0]); st.Code() == codes.OK { - t.Fatalf("Should have failed to add a generated column to non-empty table\n status: %v", st) + if st := db.ApplyDDL(ddl.List[0]); st.Code() != codes.OK { + t.Fatalf("Failed to add a generated column to non-empty table\n status: %v", st) } } diff --git a/spanner/spannertest/funcs.go b/spanner/spannertest/funcs.go index a27eab5e7be..3c451a8b7cd 100644 --- a/spanner/spannertest/funcs.go +++ b/spanner/spannertest/funcs.go @@ -88,6 +88,25 @@ var functions = map[string]function{ return cast(values, types, true) }, }, + "JSON_VALUE": { + Eval: func(values []interface{}, types []spansql.Type) (interface{}, spansql.Type, error) { + if len(values) != 2 { + return nil, spansql.Type{}, status.Error(codes.InvalidArgument, "No matching signature for function JSON_VALUE for the given argument types") + } + if values[0] == nil || values[1] == nil { + return nil, spansql.Type{Base: spansql.String}, nil + } + _, okArg1 := values[0].(string) + _, okArg2 := values[1].(string) + if !(okArg1 && okArg2) { + return nil, spansql.Type{}, status.Error(codes.InvalidArgument, "No matching signature for function JSON_VALUE for the given argument types") + } + // This function currently has no implementation and always returns + // an empty string, as it would otherwise require an XPath query + // engine. + return "", spansql.Type{Base: spansql.String}, nil + }, + }, } func cast(values []interface{}, types []spansql.Type, safe bool) (interface{}, spansql.Type, error) { diff --git a/spanner/spannertest/integration_test.go b/spanner/spannertest/integration_test.go index c5d5c81a328..4d962c5aaa0 100644 --- a/spanner/spannertest/integration_test.go +++ b/spanner/spannertest/integration_test.go @@ -1312,8 +1312,8 @@ func TestIntegration_GeneratedColumns(t *testing.T) { err = updateDDL(t, adminClient, `ALTER TABLE `+tableName+` ADD COLUMN TotalSales2 INT64 AS (NumSongs * EstimatedSales) STORED`) - if err == nil { - t.Fatalf("Should have failed to add a generated column to non empty table") + if err != nil { + t.Fatalf("Failed to add a generated column to a non-empty table: %v", err) } ri := client.Single().Query(ctx, spanner.NewStatement( diff --git a/spanner/spansql/keywords.go b/spanner/spansql/keywords.go index 61ac0657c49..0ddade0b1b3 100644 --- a/spanner/spansql/keywords.go +++ b/spanner/spansql/keywords.go @@ -231,4 +231,7 @@ var allFuncs = []string{ "UNIX_MILLIS", "UNIX_MICROS", "PENDING_COMMIT_TIMESTAMP", + + // JSON functions. + "JSON_VALUE", } diff --git a/spanner/spansql/parser_test.go b/spanner/spansql/parser_test.go index 966e4c5d1f0..429355dbec6 100644 --- a/spanner/spansql/parser_test.go +++ b/spanner/spansql/parser_test.go @@ -862,6 +862,21 @@ func TestParseDDL(t *testing.T) { }, }, }}, + {`ALTER TABLE products ADD COLUMN item STRING(MAX) AS (JSON_VALUE(itemDetails, '$.itemDetails')) STORED`, &DDL{Filename: "filename", List: []DDLStmt{ + &AlterTable{ + Name: "products", + Alteration: AddColumn{Def: ColumnDef{ + Name: "item", + Type: Type{Base: String, Len: MaxLen}, + Position: line(1), + Generated: Func{ + Name: "JSON_VALUE", + Args: []Expr{ID("itemDetails"), StringLiteral("$.itemDetails")}, + }, + }}, + Position: line(1), + }, + }}}, } for _, test := range tests { got, err := ParseDDL("filename", test.in)