Skip to content

Commit

Permalink
[v1] Support generated columns (#72)
Browse files Browse the repository at this point in the history
* Support generated columns. Update templates to not try to update generated columns as they are non-writable

* Remove Tests table from target. It's not created by yo test
  • Loading branch information
tyamagu2 authored Jul 22, 2021
1 parent 072917f commit 52b03d9
Show file tree
Hide file tree
Showing 25 changed files with 1,044 additions and 193 deletions.
1 change: 1 addition & 0 deletions loaders/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ func (s *SpannerLoaderFromDDL) ColumnList(name string) ([]*models.Column, error)
DataType: c.Type.SQL(),
NotNull: c.NotNull,
IsPrimaryKey: pk,
IsGenerated: c.GeneratedExpr != nil,
})
}

Expand Down
6 changes: 5 additions & 1 deletion loaders/spanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,8 @@ func spanTableColumns(client *spanner.Client, table string) ([]*models.Column, e
` WHERE ic.TABLE_SCHEMA = "" and ic.TABLE_NAME = c.TABLE_NAME ` +
` AND ic.COLUMN_NAME = c.COLUMN_NAME` +
` AND ic.INDEX_NAME = "PRIMARY_KEY" ` +
`) IS_PRIMARY_KEY ` +
`) IS_PRIMARY_KEY, ` +
`IS_GENERATED = "ALWAYS" AS IS_GENERATED ` +
`FROM INFORMATION_SCHEMA.COLUMNS c ` +
`WHERE c.TABLE_SCHEMA = "" AND c.TABLE_NAME = @table ` +
`ORDER BY c.ORDINAL_POSITION`
Expand Down Expand Up @@ -277,6 +278,9 @@ func spanTableColumns(client *spanner.Client, table string) ([]*models.Column, e
if err := row.ColumnByName("IS_PRIMARY_KEY", &c.IsPrimaryKey); err != nil {
return nil, err
}
if err := row.ColumnByName("IS_GENERATED", &c.IsGenerated); err != nil {
return nil, err
}

res = append(res, &c)
}
Expand Down
1 change: 1 addition & 0 deletions models/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Column struct {
DataType string // data_type
NotNull bool // not_null
IsPrimaryKey bool // is_primary_key
IsGenerated bool // is_generated
}

// Index represents an index.
Expand Down
25 changes: 16 additions & 9 deletions templates/type.go.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ func {{ .Name }}Columns() []string {
}
}

func {{ .Name }}WritableColumns() []string {
return []string{
{{- range .Fields }}
{{- if not .Col.IsGenerated }}
"{{ colname .Col }}",
{{- end }}
{{- end }}
}
}

func ({{ $short }} *{{ .Name }}) columnsToPtrs(cols []string, customPtrs map[string]interface{}) ([]interface{}, error) {
ret := make([]interface{}, 0, len(cols))
for _, col := range cols {
Expand Down Expand Up @@ -111,27 +121,24 @@ func new{{ .Name }}_Decoder(cols []string) func(*spanner.Row) (*{{ .Name }}, err
// Insert returns a Mutation to insert a row into a table. If the row already
// exists, the write or transaction fails.
func ({{ $short }} *{{ .Name }}) Insert(ctx context.Context) *spanner.Mutation {
return spanner.Insert("{{ $table }}", {{ .Name }}Columns(), []interface{}{
{{ fieldnames .Fields $short }},
})
values, _ := {{ $short }}.columnsToValues({{ .Name }}WritableColumns())
return spanner.Insert("{{ $table }}", {{ .Name }}WritableColumns(), values)
}

{{ if ne (fieldnames .Fields $short .PrimaryKeyFields) "" }}
// Update returns a Mutation to update a row in a table. If the row does not
// already exist, the write or transaction fails.
func ({{ $short }} *{{ .Name }}) Update(ctx context.Context) *spanner.Mutation {
return spanner.Update("{{ $table }}", {{ .Name }}Columns(), []interface{}{
{{ fieldnames .Fields $short }},
})
values, _ := {{ $short }}.columnsToValues({{ .Name }}WritableColumns())
return spanner.Update("{{ $table }}", {{ .Name }}WritableColumns(), values)
}

// InsertOrUpdate returns a Mutation to insert a row into a table. If the row
// already exists, it updates it instead. Any column values not explicitly
// written are preserved.
func ({{ $short }} *{{ .Name }}) InsertOrUpdate(ctx context.Context) *spanner.Mutation {
return spanner.InsertOrUpdate("{{ $table }}", {{ .Name }}Columns(), []interface{}{
{{ fieldnames .Fields $short }},
})
values, _ := {{ $short }}.columnsToValues({{ .Name }}WritableColumns())
return spanner.InsertOrUpdate("{{ $table }}", {{ .Name }}WritableColumns(), values)
}

// UpdateColumns returns a Mutation to update specified columns of a row in a table.
Expand Down
86 changes: 86 additions & 0 deletions test/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,92 @@ func TestCustomCompositePrimaryKey(t *testing.T) {
})
}

func TestGeneratedColumn(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

gc := &models.GeneratedColumn{
ID: 300,
FirstName: "John",
LastName: "Doe",
}

if _, err := client.Apply(ctx, []*spanner.Mutation{gc.Insert(ctx)}); err != nil {
t.Fatalf("Apply failed: %v", err)
}

t.Run("Insert", func(t *testing.T) {
got, err := models.FindGeneratedColumn(ctx, client.Single(), 300)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

want := &models.GeneratedColumn{
ID: 300,
FirstName: "John",
LastName: "Doe",
FullName: "John Doe",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("(-got, +want)\n%s", diff)
}
})

t.Run("Update", func(t *testing.T) {
gc := &models.GeneratedColumn{
ID: 300,
FirstName: "Jane",
LastName: "Doe",
}

if _, err := client.Apply(ctx, []*spanner.Mutation{gc.Update(ctx)}); err != nil {
t.Fatalf("Apply failed: %v", err)
}

got, err := models.FindGeneratedColumn(ctx, client.Single(), 300)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

want := &models.GeneratedColumn{
ID: 300,
FirstName: "Jane",
LastName: "Doe",
FullName: "Jane Doe",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("(-got, +want)\n%s", diff)
}
})

t.Run("InsertOrUpdate", func(t *testing.T) {
gc := &models.GeneratedColumn{
ID: 300,
FirstName: "Paul",
LastName: "Doe",
}

if _, err := client.Apply(ctx, []*spanner.Mutation{gc.InsertOrUpdate(ctx)}); err != nil {
t.Fatalf("Apply failed: %v", err)
}

got, err := models.FindGeneratedColumn(ctx, client.Single(), 300)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}

want := &models.GeneratedColumn{
ID: 300,
FirstName: "Paul",
LastName: "Doe",
FullName: "Paul Doe",
}
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("(-got, +want)\n%s", diff)
}
})
}

func TestSessionNotFound(t *testing.T) {
dbName := testutil.DatabaseName(spannerProjectName, spannerInstanceName, spannerDatabaseName)

Expand Down
7 changes: 7 additions & 0 deletions test/testdata/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,10 @@ CREATE TABLE FereignItems (
Category INT64 NOT NULL,
CONSTRAINT FK_ItemID_ForeignItems FOREIGN KEY (ItemID) REFERENCES Items (ID)
) PRIMARY KEY (ID);

CREATE TABLE GeneratedColumns (
ID INT64 NOT NULL,
FirstName STRING(50) NOT NULL,
LastName STRING(50) NOT NULL,
FullName STRING(100) NOT NULL AS (ARRAY_TO_STRING([FirstName, LastName], " ")) STORED,
) PRIMARY KEY (ID);
27 changes: 18 additions & 9 deletions test/testmodels/customtypes/compositeprimarykey.yo.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 14 additions & 9 deletions test/testmodels/customtypes/fereignitem.yo.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 40 additions & 9 deletions test/testmodels/customtypes/fulltype.yo.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 52b03d9

Please sign in to comment.