diff --git a/internal/loader.go b/internal/loader.go index c627f62..8dd728b 100644 --- a/internal/loader.go +++ b/internal/loader.go @@ -130,12 +130,47 @@ func (tl *TypeLoader) LoadTable(args *ArgType) (map[string]*Type, error) { return nil, err } + if err := tl.loadPrimaryKeys(typeTpl); err != nil { + return nil, err + } + tableMap[ti.TableName] = typeTpl } return tableMap, nil } +// loadPrimaryKeys loads primary key fields +func (tl *TypeLoader) loadPrimaryKeys(typeTpl *Type) error { + // reorder primary keys + indexCols, err := tl.loader.IndexColumnList(typeTpl.Table.TableName, "PRIMARY_KEY") + if err != nil { + panic(err) + } + + var fields []*Field + for _, idx := range indexCols { + var field *Field + for _, f := range typeTpl.Fields { + if f.Col.ColumnName == idx.ColumnName { + field = f + break + } + } + + if field == nil { + return fmt.Errorf("primary key column is not found in column list: table=%v column=%v", + typeTpl.Name, idx.ColumnName, + ) + } + fields = append(fields, field) + } + + typeTpl.PrimaryKey = fields[0] // backward compatibility + typeTpl.PrimaryKeyFields = fields + return nil +} + // tableCustomTypes find custom type definitions of the table func (tl *TypeLoader) tableCustomTypes(table string) map[string]string { var columnTypes map[string]string @@ -198,13 +233,6 @@ func (tl *TypeLoader) LoadColumns(args *ArgType, typeTpl *Type) error { } } - // set primary key - if c.IsPrimaryKey { - typeTpl.PrimaryKeyFields = append(typeTpl.PrimaryKeyFields, f) - // This is retained for backward compatibility in the templates. - typeTpl.PrimaryKey = f - } - // append col to template fields typeTpl.Fields = append(typeTpl.Fields, f) } @@ -264,11 +292,6 @@ func (tl *TypeLoader) LoadTableIndexes(args *ArgType, typeTpl *Type, ixMap map[s ixMap[typeTpl.Table.TableName+"_"+ix.IndexName] = ixTpl } - // search for primary key if it was skipped being set in the type - if typeTpl.PrimaryKey == nil { - return fmt.Errorf("no primary key found for %v", typeTpl.Name) - } - return nil } diff --git a/loaders/parser.go b/loaders/parser.go index 788b11d..0a344b5 100644 --- a/loaders/parser.go +++ b/loaders/parser.go @@ -142,6 +142,10 @@ func (s *SpannerLoaderFromDDL) IndexList(name string) ([]*models.Index, error) { } func (s *SpannerLoaderFromDDL) IndexColumnList(table, index string) ([]*models.IndexColumn, error) { + if index == "PRIMARY_KEY" { + return s.primaryKeyColumnList(table) + } + var cols []*models.IndexColumn for _, ix := range s.tables[table].createIndexes { if ix.Name.Name != index { @@ -170,3 +174,20 @@ func (s *SpannerLoaderFromDDL) IndexColumnList(table, index string) ([]*models.I return cols, nil } + +func (s *SpannerLoaderFromDDL) primaryKeyColumnList(table string) ([]*models.IndexColumn, error) { + tbl, ok := s.tables[table] + if !ok { + return nil, nil + } + + var cols []*models.IndexColumn + for i, key := range tbl.createTable.PrimaryKeys { + cols = append(cols, &models.IndexColumn{ + SeqNo: i + 1, + ColumnName: key.Name.Name, + }) + } + + return cols, nil +} diff --git a/test/testdata/schema.sql b/test/testdata/schema.sql index b82a401..a377bfa 100644 --- a/test/testdata/schema.sql +++ b/test/testdata/schema.sql @@ -32,6 +32,12 @@ CREATE INDEX CompositePrimaryKeysByError ON CompositePrimaryKeys(Error); CREATE INDEX CompositePrimaryKeysByError2 ON CompositePrimaryKeys(Error) STORING(Z); CREATE INDEX CompositePrimaryKeysByError3 ON CompositePrimaryKeys(Error) STORING(Z, Y); +CREATE TABLE OutOfOrderPrimaryKeys ( + PKey1 STRING(32) NOT NULL, + PKey2 STRING(32) NOT NULL, + PKey3 STRING(32) NOT NULL, +) PRIMARY KEY(PKey2, PKey1, PKey3); + CREATE TABLE FullTypes ( PKey STRING(32) NOT NULL, FTString STRING(32) NOT NULL, diff --git a/test/testmodels/customtypes/outoforderprimarykey.yo.go b/test/testmodels/customtypes/outoforderprimarykey.yo.go new file mode 100644 index 0000000..d80104e --- /dev/null +++ b/test/testmodels/customtypes/outoforderprimarykey.yo.go @@ -0,0 +1,107 @@ +// Code generated by yo. DO NOT EDIT. +// Package customtypes contains the types. +package customtypes + +import ( + "context" + "fmt" + + "cloud.google.com/go/spanner" +) + +// OutOfOrderPrimaryKey represents a row from 'OutOfOrderPrimaryKeys'. +type OutOfOrderPrimaryKey struct { + PKey1 string `spanner:"PKey1" json:"PKey1"` // PKey1 + PKey2 string `spanner:"PKey2" json:"PKey2"` // PKey2 + PKey3 string `spanner:"PKey3" json:"PKey3"` // PKey3 +} + +func OutOfOrderPrimaryKeyPrimaryKeys() []string { + return []string{ + "PKey2", + "PKey1", + "PKey3", + } +} + +func OutOfOrderPrimaryKeyColumns() []string { + return []string{ + "PKey1", + "PKey2", + "PKey3", + } +} + +func (ooopk *OutOfOrderPrimaryKey) columnsToPtrs(cols []string, customPtrs map[string]interface{}) ([]interface{}, error) { + ret := make([]interface{}, 0, len(cols)) + for _, col := range cols { + if val, ok := customPtrs[col]; ok { + ret = append(ret, val) + continue + } + + switch col { + case "PKey1": + ret = append(ret, &ooopk.PKey1) + case "PKey2": + ret = append(ret, &ooopk.PKey2) + case "PKey3": + ret = append(ret, &ooopk.PKey3) + default: + return nil, fmt.Errorf("unknown column: %s", col) + } + } + return ret, nil +} + +func (ooopk *OutOfOrderPrimaryKey) columnsToValues(cols []string) ([]interface{}, error) { + ret := make([]interface{}, 0, len(cols)) + for _, col := range cols { + switch col { + case "PKey1": + ret = append(ret, ooopk.PKey1) + case "PKey2": + ret = append(ret, ooopk.PKey2) + case "PKey3": + ret = append(ret, ooopk.PKey3) + default: + return nil, fmt.Errorf("unknown column: %s", col) + } + } + + return ret, nil +} + +// newOutOfOrderPrimaryKey_Decoder returns a decoder which reads a row from *spanner.Row +// into OutOfOrderPrimaryKey. The decoder is not goroutine-safe. Don't use it concurrently. +func newOutOfOrderPrimaryKey_Decoder(cols []string) func(*spanner.Row) (*OutOfOrderPrimaryKey, error) { + customPtrs := map[string]interface{}{} + + return func(row *spanner.Row) (*OutOfOrderPrimaryKey, error) { + var ooopk OutOfOrderPrimaryKey + ptrs, err := ooopk.columnsToPtrs(cols, customPtrs) + if err != nil { + return nil, err + } + + if err := row.Columns(ptrs...); err != nil { + return nil, err + } + + return &ooopk, nil + } +} + +// Insert returns a Mutation to insert a row into a table. If the row already +// exists, the write or transaction fails. +func (ooopk *OutOfOrderPrimaryKey) Insert(ctx context.Context) *spanner.Mutation { + return spanner.Insert("OutOfOrderPrimaryKeys", OutOfOrderPrimaryKeyColumns(), []interface{}{ + ooopk.PKey1, ooopk.PKey2, ooopk.PKey3, + }) +} + +// Delete deletes the OutOfOrderPrimaryKey from the database. +func (ooopk *OutOfOrderPrimaryKey) Delete(ctx context.Context) *spanner.Mutation { + values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyPrimaryKeys()) + return spanner.Delete("OutOfOrderPrimaryKeys", spanner.Key(values)) +} diff --git a/test/testmodels/default/outoforderprimarykey.yo.go b/test/testmodels/default/outoforderprimarykey.yo.go new file mode 100644 index 0000000..5528cb0 --- /dev/null +++ b/test/testmodels/default/outoforderprimarykey.yo.go @@ -0,0 +1,107 @@ +// Code generated by yo. DO NOT EDIT. +// Package models contains the types. +package models + +import ( + "context" + "fmt" + + "cloud.google.com/go/spanner" +) + +// OutOfOrderPrimaryKey represents a row from 'OutOfOrderPrimaryKeys'. +type OutOfOrderPrimaryKey struct { + PKey1 string `spanner:"PKey1" json:"PKey1"` // PKey1 + PKey2 string `spanner:"PKey2" json:"PKey2"` // PKey2 + PKey3 string `spanner:"PKey3" json:"PKey3"` // PKey3 +} + +func OutOfOrderPrimaryKeyPrimaryKeys() []string { + return []string{ + "PKey2", + "PKey1", + "PKey3", + } +} + +func OutOfOrderPrimaryKeyColumns() []string { + return []string{ + "PKey1", + "PKey2", + "PKey3", + } +} + +func (ooopk *OutOfOrderPrimaryKey) columnsToPtrs(cols []string, customPtrs map[string]interface{}) ([]interface{}, error) { + ret := make([]interface{}, 0, len(cols)) + for _, col := range cols { + if val, ok := customPtrs[col]; ok { + ret = append(ret, val) + continue + } + + switch col { + case "PKey1": + ret = append(ret, &ooopk.PKey1) + case "PKey2": + ret = append(ret, &ooopk.PKey2) + case "PKey3": + ret = append(ret, &ooopk.PKey3) + default: + return nil, fmt.Errorf("unknown column: %s", col) + } + } + return ret, nil +} + +func (ooopk *OutOfOrderPrimaryKey) columnsToValues(cols []string) ([]interface{}, error) { + ret := make([]interface{}, 0, len(cols)) + for _, col := range cols { + switch col { + case "PKey1": + ret = append(ret, ooopk.PKey1) + case "PKey2": + ret = append(ret, ooopk.PKey2) + case "PKey3": + ret = append(ret, ooopk.PKey3) + default: + return nil, fmt.Errorf("unknown column: %s", col) + } + } + + return ret, nil +} + +// newOutOfOrderPrimaryKey_Decoder returns a decoder which reads a row from *spanner.Row +// into OutOfOrderPrimaryKey. The decoder is not goroutine-safe. Don't use it concurrently. +func newOutOfOrderPrimaryKey_Decoder(cols []string) func(*spanner.Row) (*OutOfOrderPrimaryKey, error) { + customPtrs := map[string]interface{}{} + + return func(row *spanner.Row) (*OutOfOrderPrimaryKey, error) { + var ooopk OutOfOrderPrimaryKey + ptrs, err := ooopk.columnsToPtrs(cols, customPtrs) + if err != nil { + return nil, err + } + + if err := row.Columns(ptrs...); err != nil { + return nil, err + } + + return &ooopk, nil + } +} + +// Insert returns a Mutation to insert a row into a table. If the row already +// exists, the write or transaction fails. +func (ooopk *OutOfOrderPrimaryKey) Insert(ctx context.Context) *spanner.Mutation { + return spanner.Insert("OutOfOrderPrimaryKeys", OutOfOrderPrimaryKeyColumns(), []interface{}{ + ooopk.PKey1, ooopk.PKey2, ooopk.PKey3, + }) +} + +// Delete deletes the OutOfOrderPrimaryKey from the database. +func (ooopk *OutOfOrderPrimaryKey) Delete(ctx context.Context) *spanner.Mutation { + values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyPrimaryKeys()) + return spanner.Delete("OutOfOrderPrimaryKeys", spanner.Key(values)) +} diff --git a/test/testmodels/single/single_file.go b/test/testmodels/single/single_file.go index 6861852..f0c77ea 100644 --- a/test/testmodels/single/single_file.go +++ b/test/testmodels/single/single_file.go @@ -1010,6 +1010,103 @@ func (ml *MaxLength) Delete(ctx context.Context) *spanner.Mutation { return spanner.Delete("MaxLengths", spanner.Key(values)) } +// OutOfOrderPrimaryKey represents a row from 'OutOfOrderPrimaryKeys'. +type OutOfOrderPrimaryKey struct { + PKey1 string `spanner:"PKey1" json:"PKey1"` // PKey1 + PKey2 string `spanner:"PKey2" json:"PKey2"` // PKey2 + PKey3 string `spanner:"PKey3" json:"PKey3"` // PKey3 +} + +func OutOfOrderPrimaryKeyPrimaryKeys() []string { + return []string{ + "PKey2", + "PKey1", + "PKey3", + } +} + +func OutOfOrderPrimaryKeyColumns() []string { + return []string{ + "PKey1", + "PKey2", + "PKey3", + } +} + +func (ooopk *OutOfOrderPrimaryKey) columnsToPtrs(cols []string, customPtrs map[string]interface{}) ([]interface{}, error) { + ret := make([]interface{}, 0, len(cols)) + for _, col := range cols { + if val, ok := customPtrs[col]; ok { + ret = append(ret, val) + continue + } + + switch col { + case "PKey1": + ret = append(ret, &ooopk.PKey1) + case "PKey2": + ret = append(ret, &ooopk.PKey2) + case "PKey3": + ret = append(ret, &ooopk.PKey3) + default: + return nil, fmt.Errorf("unknown column: %s", col) + } + } + return ret, nil +} + +func (ooopk *OutOfOrderPrimaryKey) columnsToValues(cols []string) ([]interface{}, error) { + ret := make([]interface{}, 0, len(cols)) + for _, col := range cols { + switch col { + case "PKey1": + ret = append(ret, ooopk.PKey1) + case "PKey2": + ret = append(ret, ooopk.PKey2) + case "PKey3": + ret = append(ret, ooopk.PKey3) + default: + return nil, fmt.Errorf("unknown column: %s", col) + } + } + + return ret, nil +} + +// newOutOfOrderPrimaryKey_Decoder returns a decoder which reads a row from *spanner.Row +// into OutOfOrderPrimaryKey. The decoder is not goroutine-safe. Don't use it concurrently. +func newOutOfOrderPrimaryKey_Decoder(cols []string) func(*spanner.Row) (*OutOfOrderPrimaryKey, error) { + customPtrs := map[string]interface{}{} + + return func(row *spanner.Row) (*OutOfOrderPrimaryKey, error) { + var ooopk OutOfOrderPrimaryKey + ptrs, err := ooopk.columnsToPtrs(cols, customPtrs) + if err != nil { + return nil, err + } + + if err := row.Columns(ptrs...); err != nil { + return nil, err + } + + return &ooopk, nil + } +} + +// Insert returns a Mutation to insert a row into a table. If the row already +// exists, the write or transaction fails. +func (ooopk *OutOfOrderPrimaryKey) Insert(ctx context.Context) *spanner.Mutation { + return spanner.Insert("OutOfOrderPrimaryKeys", OutOfOrderPrimaryKeyColumns(), []interface{}{ + ooopk.PKey1, ooopk.PKey2, ooopk.PKey3, + }) +} + +// Delete deletes the OutOfOrderPrimaryKey from the database. +func (ooopk *OutOfOrderPrimaryKey) Delete(ctx context.Context) *spanner.Mutation { + values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyPrimaryKeys()) + return spanner.Delete("OutOfOrderPrimaryKeys", spanner.Key(values)) +} + // SnakeCase represents a row from 'snake_cases'. type SnakeCase struct { ID int64 `spanner:"id" json:"id"` // id