Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ jobs:
- run:
name: regenerate templates and check diff
command: |
set -x
ls -ltr tplbin/
rm tplbin/templates.go
go-assets-builder --package=tplbin --strip-prefix="/templates/" --output tplbin/templates.go templates/*.tpl
make regen
git diff tplbin/ | cat
git diff --quiet tplbin/

- run:
Expand Down
15 changes: 12 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ all: build
build: regen ## build yo command and regenerate template bin
go build

regen: tplbin/templates.go ## regenerate template bin
.PHONY: regen
regen:
rm tplbin/templates.go
$(MAKE) tplbin/templates.go ## regenerate template bin

deps:
go get -u github.com/jessevdk/go-assets-builder
go install github.com/jessevdk/go-assets-builder@latest

.PHONY: gomod
gomod: ## Run go mod
Expand Down Expand Up @@ -61,25 +64,31 @@ testdata/customtypes:
rm -rf test/testmodels/customtypes && mkdir -p test/testmodels/customtypes
$(YOBIN) $(SPANNER_PROJECT_NAME) $(SPANNER_INSTANCE_NAME) $(SPANNER_DATABASE_NAME) --custom-types-file test/testdata/custom_column_types.yml --out test/testmodels/customtypes/

.PHONY: testdata-from-ddl
testdata-from-ddl:
$(MAKE) -j4 testdata-from-ddl/default testdata-from-ddl/underscore testdata-from-ddl/customtypes testdata-from-ddl/single

.PHONY: testdata-from-ddl/default
testdata-from-ddl/default:
rm -rf test/testmodels/default && mkdir -p test/testmodels/default
$(YOBIN) generate ./test/testdata/schema.sql --from-ddl --package models --out test/testmodels/default/

.PHONY: testdata-from-ddl/underscore
testdata-from-ddl/underscore:
rm -rf test/testmodels/underscore && mkdir -p test/testmodels/underscore
$(YOBIN) generate ./test/testdata/schema.sql --from-ddl --package models --underscore --out test/testmodels/underscore/

.PHONY: testdata-from-ddl/single
testdata-from-ddl/single:
rm -rf test/testmodels/single && mkdir -p test/testmodels/single
$(YOBIN) generate ./test/testdata/schema.sql --from-ddl --out test/testmodels/single/single_file.go --single-file

.PHONY: testdata-from-ddl/customtypes
testdata-from-ddl/customtypes:
rm -rf test/testmodels/customtypes && mkdir -p test/testmodels/customtypes
$(YOBIN) generate ./test/testdata/schema.sql --from-ddl --custom-types-file test/testdata/custom_column_types.yml --out test/testmodels/customtypes/

.PHONY: recreate-templates
recreate-templates:: ## recreate templates
rm -rf templates && mkdir templates
$(YOBIN) create-template --template-path templates
Expand Down Expand Up @@ -125,4 +134,4 @@ check-diff:
echo "--- Actual Files ---" ; \
echo "$$ACTUAL_FILES" ; \
exit 1 ; \
fi
fi
10 changes: 5 additions & 5 deletions tplbin/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,31 @@ import (
"github.com/jessevdk/go-assets"
)

var _Assets2da36312f867e2e1a26f5a29c883fe2d56891890 = "// Code generated by yo. DO NOT EDIT.\n// Package {{ .Package }} contains the types.\npackage {{ .Package }}\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"cloud.google.com/go/spanner\"\n\t\"google.golang.org/api/iterator\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n"
var _Assets35fa065605f72dabb3fd17747217ebb391a6a686 = "{{- $short := (shortname .Type.Name \"err\" \"sqlstr\" \"db\" \"q\" \"res\" \"YOLog\" .Fields) -}}\n{{- $table := (.Type.Table.TableName) -}}\n{{- if not .Index.IsUnique }}\n// Find{{ .FuncName }} retrieves multiple rows from '{{ $table }}' as a slice of {{ .Type.Name }}.\n//\n// Generated from index '{{ .Index.IndexName }}'.\nfunc Find{{ .FuncName }}(ctx context.Context, db YORODB{{ gocustomparamlist .Fields true true }}) ([]*{{ .Type.Name }}, error) {\n{{- else }}\n// Find{{ .FuncName }} retrieves a row from '{{ $table }}' as a {{ .Type.Name }}.\n//\n// If no row is present with the given key, then ReadRow returns an error where\n// spanner.ErrCode(err) is codes.NotFound.\n//\n// Generated from unique index '{{ .Index.IndexName }}'.\nfunc Find{{ .FuncName }}(ctx context.Context, db YORODB{{ gocustomparamlist .Fields true true }}) (*{{ .Type.Name }}, error) {\n{{- end }}\n\t{{- if not .NullableFields }}\n\tconst sqlstr = \"SELECT \" +\n\t\t\"{{ escapedcolnames .Type.Fields }} \" +\n\t\t\"FROM {{ $table }}@{FORCE_INDEX={{ .Index.IndexName }}} \" +\n\t\t\"WHERE {{ colnamesquery .Fields \" AND \" }}\"\n\t{{- else }}\n\tvar sqlstr = \"SELECT \" +\n\t\t\"{{ escapedcolnames .Type.Fields }} \" +\n\t\t\"FROM {{ $table }}@{FORCE_INDEX={{ .Index.IndexName }}} \"\n\n\tconds := make([]string, {{ columncount .Fields }})\n\t{{- range $i, $f := .Fields }}\n\t{{- if $f.Col.NotNull }}\n\t\tconds[{{ $i }}] = \"{{ escapedcolname $f.Col }} = @param{{ $i }}\"\n\t{{- else }}\n\tif {{ nullcheck $f }} {\n\t\tconds[{{ $i }}] = \"{{ escapedcolname $f.Col }} IS NULL\"\n\t} else {\n\t\tconds[{{ $i }}] = \"{{ escapedcolname $f.Col }} = @param{{ $i }}\"\n\t}\n\t{{- end }}\n\t{{- end }}\n\tsqlstr += \"WHERE \" + strings.Join(conds, \" AND \")\n\t{{- end }}\n\n\tstmt := spanner.NewStatement(sqlstr)\n\t{{- range $i, $f := .Fields }}\n\t\t{{- if $f.CustomType }}\n\t\t\tstmt.Params[\"param{{ $i }}\"] = {{ $f.Type }}({{ goparamname $f.Name }})\n\t\t{{- else }}\n\t\t\tstmt.Params[\"param{{ $i }}\"] = {{ goparamname $f.Name }}\n\t\t{{- end }}\n\t{{- end}}\n\n\n\tdecoder := new{{ .Type.Name }}_Decoder({{ .Type.Name }}Columns())\n\n\t// run query\n\tYOLog(ctx, sqlstr{{ goparamlist .Fields true false }})\n{{- if .Index.IsUnique }}\n\titer := db.Query(ctx, stmt)\n\tdefer iter.Stop()\n\n\trow, err := iter.Next()\n\tif err != nil {\n\t\tif err == iterator.Done {\n\t\t\treturn nil, newErrorWithCode(codes.NotFound, \"Find{{ .FuncName }}\", \"{{ $table }}\", err)\n\t\t}\n\t\treturn nil, newError(\"Find{{ .FuncName }}\", \"{{ $table }}\", err)\n\t}\n\n\t{{ $short }}, err := decoder(row)\n\tif err != nil {\n\t\treturn nil, newErrorWithCode(codes.Internal, \"Find{{ .FuncName }}\", \"{{ $table }}\", err)\n\t}\n\n\treturn {{ $short }}, nil\n{{- else }}\n\titer := db.Query(ctx, stmt)\n\tdefer iter.Stop()\n\n\t// load results\n\tres := []*{{ .Type.Name }}{}\n\tfor {\n\t\trow, err := iter.Next()\n\t\tif err != nil {\n\t\t\tif err == iterator.Done {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\treturn nil, newError(\"Find{{ .FuncName }}\", \"{{ $table }}\", err)\n\t\t}\n\n\t\t{{ $short }}, err := decoder(row)\n if err != nil {\n return nil, newErrorWithCode(codes.Internal, \"Find{{ .FuncName }}\", \"{{ $table }}\", err)\n }\n\n\t\tres = append(res, {{ $short }})\n\t}\n\n\treturn res, nil\n{{- end }}\n}\n\n\n// Read{{ .FuncName }} retrieves multiples rows from '{{ $table }}' by KeySet as a slice.\n//\n// This does not retrieve all columns of '{{ $table }}' because an index has only columns\n// used for primary key, index key and storing columns. If you need more columns, add storing\n// columns or Read by primary key or Query with join.\n//\n// Generated from unique index '{{ .Index.IndexName }}'.\nfunc Read{{ .FuncName }}(ctx context.Context, db YORODB, keys spanner.KeySet) ([]*{{ .Type.Name }}, error) {\n\tvar res []*{{ .Type.Name }}\n columns := []string{\n{{- range .Type.PrimaryKeyFields }}\n\t\t\"{{ colname .Col }}\",\n{{- end }}\n{{- range .Fields }}\n\t\t\"{{ colname .Col }}\",\n{{- end }}\n{{- range .StoringFields }}\n\t\t\"{{ colname .Col }}\",\n{{- end }}\n}\n\n\tdecoder := new{{ .Type.Name }}_Decoder(columns)\n\n\trows := db.ReadUsingIndex(ctx, \"{{ $table }}\", \"{{ .Index.IndexName }}\", keys, columns)\n\terr := rows.Do(func(row *spanner.Row) error {\n\t\t{{ $short }}, err := decoder(row)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tres = append(res, {{ $short }})\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, newErrorWithCode(codes.Internal, \"Read{{ .FuncName }}\", \"{{ $table }}\", err)\n\t}\n\n return res, nil\n}\n\n"
var _Assets7fd73945d69f17ee7478fe75c9ebb3a425327b99 = "{{- $short := (shortname .Name \"err\" \"res\" \"sqlstr\" \"db\" \"YOLog\") -}}\n{{- $table := (.Table.TableName) -}}\n// {{ .Name }} represents a row from '{{ $table }}'.\ntype {{ .Name }} struct {\n{{- range .Fields }}\n{{- if not .Col.IsHidden }}\n {{- if eq (.Col.DataType) (.Col.ColumnName) }}\n {{ .Name }} string `spanner:\"{{ .Col.ColumnName }}\" json:\"{{ .Col.ColumnName }}\"` // {{ .Col.ColumnName }} enum\n {{- else if .CustomType }}\n {{ .Name }} {{ retype .CustomType }} `spanner:\"{{ .Col.ColumnName }}\" json:\"{{ .Col.ColumnName }}\"` // {{ .Col.ColumnName }}\n {{- else }}\n {{ .Name }} {{ .Type }} `spanner:\"{{ .Col.ColumnName }}\" json:\"{{ .Col.ColumnName }}\"` // {{ .Col.ColumnName }}\n {{- end }}\n {{- end }}\n{{- end }}\n}\n\n{{ if .PrimaryKey }}\nfunc {{ .Name }}PrimaryKeys() []string {\n return []string{\n{{- range .PrimaryKeyFields }}\n\t\t\"{{ colname .Col }}\",\n{{- end }}\n\t}\n}\n{{- end }}\n\nfunc {{ .Name }}Columns() []string {\n\treturn []string{\n{{- range .Fields }}\n {{- if not .Col.IsHidden }}\n\t\t\"{{ colname .Col }}\",\n\t{{- end }}\n{{- end }}\n\t}\n}\n\nfunc {{ .Name }}WritableColumns() []string {\n\treturn []string{\n{{- range .Fields }}\n\t{{- if not .Col.IsGenerated }}\n\t\t\"{{ colname .Col }}\",\n\t{{- end }}\n{{- end }}\n\t}\n}\n\nfunc ({{ $short }} *{{ .Name }}) columnsToPtrs(cols []string, customPtrs map[string]interface{}) ([]interface{}, error) {\n\tret := make([]interface{}, 0, len(cols))\n\tfor _, col := range cols {\n\t\tif val, ok := customPtrs[col]; ok {\n\t\t\tret = append(ret, val)\n\t\t\tcontinue\n\t\t}\n\n\t\tswitch col {\n{{- range .Fields }}\n {{- if not .Col.IsHidden }}\n\t\tcase \"{{ colname .Col }}\":\n\t\t\tret = append(ret, &{{ $short }}.{{ .Name }})\n\t{{- end }}\n{{- end }}\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unknown column: %s\", col)\n\t\t}\n\t}\n\treturn ret, nil\n}\n\nfunc ({{ $short }} *{{ .Name }}) columnsToValues(cols []string) ([]interface{}, error) {\n\tret := make([]interface{}, 0, len(cols))\n\tfor _, col := range cols {\n\t\tswitch col {\n{{- range .Fields }}\n {{- if not .Col.IsHidden }}\n\t\tcase \"{{ colname .Col }}\":\n\t\t {{- if .Col.IsAllowCommitTimestamp }}\n\t\t ret = append(ret, spanner.CommitTimestamp)\n\t\t\t{{- else if .CustomType }}\n\t\t\tret = append(ret, {{ .Type }}({{ $short }}.{{ .Name }}))\n\t\t\t{{- else }}\n\t\t\tret = append(ret, {{ $short }}.{{ .Name }})\n\t\t\t{{- end }}\n {{- end }}\n{{- end }}\n\t\tdefault:\n\t\t\treturn nil, fmt.Errorf(\"unknown column: %s\", col)\n\t\t}\n\t}\n\n\treturn ret, nil\n}\n\n// new{{ .Name }}_Decoder returns a decoder which reads a row from *spanner.Row\n// into {{ .Name }}. The decoder is not goroutine-safe. Don't use it concurrently.\nfunc new{{ .Name }}_Decoder(cols []string) func(*spanner.Row) (*{{ .Name }}, error) {\n\t{{- range .Fields }}\n\t\t{{- if .CustomType }}\n\t\t\tvar {{ customtypeparam .Name }} {{ .Type }}\n\t\t{{- end }}\n\t{{- end }}\n\tcustomPtrs := map[string]interface{}{\n\t\t{{- range .Fields }}\n\t\t\t{{- if .CustomType }}\n\t\t\t\t\"{{ colname .Col }}\": &{{ customtypeparam .Name }},\n\t\t\t{{- end }}\n\t{{- end }}\n\t}\n\n\treturn func(row *spanner.Row) (*{{ .Name }}, error) {\n var {{ $short }} {{ .Name }}\n ptrs, err := {{ $short }}.columnsToPtrs(cols, customPtrs)\n if err != nil {\n return nil, err\n }\n\n if err := row.Columns(ptrs...); err != nil {\n return nil, err\n }\n {{- range .Fields }}\n {{- if .CustomType }}\n {{ $short }}.{{ .Name }} = {{ retype .CustomType }}({{ customtypeparam .Name }})\n {{- end }}\n {{- end }}\n\n\n\t\treturn &{{ $short }}, nil\n\t}\n}\n\n// Insert returns a Mutation to insert a row into a table. If the row already\n// exists, the write or transaction fails.\nfunc ({{ $short }} *{{ .Name }}) Insert(ctx context.Context) *spanner.Mutation {\n\tvalues, _ := {{ $short }}.columnsToValues({{ .Name }}WritableColumns())\n\treturn spanner.Insert(\"{{ $table }}\", {{ .Name }}WritableColumns(), values)\n}\n\n{{ if ne (fieldnames .Fields $short .PrimaryKeyFields) \"\" }}\n// Update returns a Mutation to update a row in a table. If the row does not\n// already exist, the write or transaction fails.\nfunc ({{ $short }} *{{ .Name }}) Update(ctx context.Context) *spanner.Mutation {\n\tvalues, _ := {{ $short }}.columnsToValues({{ .Name }}WritableColumns())\n\treturn spanner.Update(\"{{ $table }}\", {{ .Name }}WritableColumns(), values)\n}\n\n// InsertOrUpdate returns a Mutation to insert a row into a table. If the row\n// already exists, it updates it instead. Any column values not explicitly\n// written are preserved.\nfunc ({{ $short }} *{{ .Name }}) InsertOrUpdate(ctx context.Context) *spanner.Mutation {\n\tvalues, _ := {{ $short }}.columnsToValues({{ .Name }}WritableColumns())\n\treturn spanner.InsertOrUpdate(\"{{ $table }}\", {{ .Name }}WritableColumns(), values)\n}\n\n// UpdateColumns returns a Mutation to update specified columns of a row in a table.\nfunc ({{ $short }} *{{ .Name }}) UpdateColumns(ctx context.Context, cols ...string) (*spanner.Mutation, error) {\n\t// add primary keys to columns to update by primary keys\n\tcolsWithPKeys := append(cols, {{ .Name }}PrimaryKeys()...)\n\n\tvalues, err := {{ $short }}.columnsToValues(colsWithPKeys)\n\tif err != nil {\n\t\treturn nil, newErrorWithCode(codes.InvalidArgument, \"{{ .Name }}.UpdateColumns\", \"{{ $table }}\", err)\n\t}\n\n\treturn spanner.Update(\"{{ $table }}\", colsWithPKeys, values), nil\n}\n\n// Find{{ .Name }} gets a {{ .Name }} by primary key\nfunc Find{{ .Name }}(ctx context.Context, db YORODB{{ gocustomparamlist .PrimaryKeyFields true true }}) (*{{ .Name }}, error) {\n\tkey := spanner.Key{ {{ gocustomparamlist .PrimaryKeyFields false false }} }\n\trow, err := db.ReadRow(ctx, \"{{ $table }}\", key, {{ .Name }}Columns())\n\tif err != nil {\n\t\treturn nil, newError(\"Find{{ .Name }}\", \"{{ $table }}\", err)\n\t}\n\n\tdecoder := new{{ .Name }}_Decoder({{ .Name}}Columns())\n\t{{ $short }}, err := decoder(row)\n\tif err != nil {\n\t\treturn nil, newErrorWithCode(codes.Internal, \"Find{{ .Name }}\", \"{{ $table }}\", err)\n\t}\n\n\treturn {{ $short }}, nil\n}\n\n// Read{{ .Name }} retrieves multiples rows from {{ .Name }} by KeySet as a slice.\nfunc Read{{ .Name }}(ctx context.Context, db YORODB, keys spanner.KeySet) ([]*{{ .Name }}, error) {\n\tvar res []*{{ .Name }}\n\n\tdecoder := new{{ .Name }}_Decoder({{ .Name}}Columns())\n\n\trows := db.Read(ctx, \"{{ $table }}\", keys, {{ .Name }}Columns())\n\terr := rows.Do(func(row *spanner.Row) error {\n\t\t{{ $short }}, err := decoder(row)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tres = append(res, {{ $short }})\n\n\t\treturn nil\n\t})\n\tif err != nil {\n\t\treturn nil, newErrorWithCode(codes.Internal, \"Read{{ .Name }}\", \"{{ $table }}\", err)\n\t}\n\n\treturn res, nil\n}\n{{ end }}\n\n// Delete deletes the {{ .Name }} from the database.\nfunc ({{ $short }} *{{ .Name }}) Delete(ctx context.Context) *spanner.Mutation {\n\tvalues, _ := {{ $short }}.columnsToValues({{ .Name }}PrimaryKeys())\n\treturn spanner.Delete(\"{{ $table }}\", spanner.Key(values))\n}\n"
var _Assets652b6e36fe11372d65bfc0531de888fa9f12e2c0 = "// YODB is the common interface for database operations.\ntype YODB interface {\n\tYORODB\n}\n\n// YORODB is the common interface for database operations.\ntype YORODB interface {\n\tReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error)\n\tRead(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator\n\tReadUsingIndex(ctx context.Context, table, index string, keys spanner.KeySet, columns []string) (ri *spanner.RowIterator)\n\tQuery(ctx context.Context, statement spanner.Statement) *spanner.RowIterator\n}\n\n// YOLog provides the log func used by generated queries.\nvar YOLog = func(context.Context, string, ...interface{}) { }\n\nfunc newError(method, table string, err error) error {\n\tcode := spanner.ErrCode(err)\n\treturn newErrorWithCode(code, method, table, err)\n}\n\nfunc newErrorWithCode(code codes.Code, method, table string, err error) error {\n\treturn &yoError{\n\t\tmethod: method,\n\t\ttable: table,\n\t\terr: err,\n\t\tcode: code,\n\t}\n}\n\ntype yoError struct {\n\terr error\n\tmethod string\n\ttable string\n\tcode codes.Code\n}\n\nfunc (e yoError) Error() string {\n\treturn fmt.Sprintf(\"yo error in %s(%s): %v\", e.method, e.table, e.err)\n}\n\nfunc (e yoError) Unwrap() error {\n\treturn e.err\n}\n\nfunc (e yoError) DBTableName() string {\n\treturn e.table\n}\n\n// GRPCStatus implements a conversion to a gRPC status using `status.Convert(error)`.\n// If the error is originated from the Spanner library, this returns a gRPC status of\n// the original error. It may contain details of the status such as RetryInfo.\nfunc (e yoError) GRPCStatus() *status.Status {\n\tvar ae *apierror.APIError\n\tif errors.As(e.err, &ae) {\n\t\treturn status.Convert(ae)\n\t}\n\n\treturn status.New(e.code, e.Error())\n}\n\nfunc (e yoError) Timeout() bool { return e.code == codes.DeadlineExceeded }\nfunc (e yoError) Temporary() bool { return e.code == codes.DeadlineExceeded }\nfunc (e yoError) NotFound() bool { return e.code == codes.NotFound }\n"
var _Assets2da36312f867e2e1a26f5a29c883fe2d56891890 = "// Code generated by yo. DO NOT EDIT.\n// Package {{ .Package }} contains the types.\npackage {{ .Package }}\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\n\t\"cloud.google.com/go/spanner\"\n\t\"google.golang.org/api/iterator\"\n\t\"google.golang.org/grpc/codes\"\n\t\"google.golang.org/grpc/status\"\n)\n"

// Assets returns go-assets FileSystem
var Assets = assets.NewFileSystem(map[string][]string{}, map[string]*assets.File{
"type.go.tpl": &assets.File{
Path: "type.go.tpl",
FileMode: 0x1a4,
Mtime: time.Unix(1743588257, 1743588257909937240),
Mtime: time.Unix(1758094680, 1758094680545784620),
Data: []byte(_Assets7fd73945d69f17ee7478fe75c9ebb3a425327b99),
}, "yo_db.go.tpl": &assets.File{
Path: "yo_db.go.tpl",
FileMode: 0x1a4,
Mtime: time.Unix(1743587775, 1743587775342720016),
Mtime: time.Unix(1740038766, 1740038766258821598),
Data: []byte(_Assets652b6e36fe11372d65bfc0531de888fa9f12e2c0),
}, "yo_package.go.tpl": &assets.File{
Path: "yo_package.go.tpl",
FileMode: 0x1a4,
Mtime: time.Unix(1743587775, 1743587775342966221),
Mtime: time.Unix(1740038766, 1740038766258932972),
Data: []byte(_Assets2da36312f867e2e1a26f5a29c883fe2d56891890),
}, "index.go.tpl": &assets.File{
Path: "index.go.tpl",
FileMode: 0x1a4,
Mtime: time.Unix(1743587775, 1743587775343231176),
Mtime: time.Unix(1740038766, 1740038766258444396),
Data: []byte(_Assets35fa065605f72dabb3fd17747217ebb391a6a686),
}}, "")