├── v2 ├── .gitignore ├── test │ ├── testmodels │ │ ├── dump_types │ │ │ ├── item.txt │ │ │ ├── inflectionzz.txt │ │ │ ├── fereign_item.txt │ │ │ ├── max_length.txt │ │ │ ├── snake_case.txt │ │ │ ├── generated_column.txt │ │ │ ├── out_of_order_primary_key.txt │ │ │ ├── composite_primary_key.txt │ │ │ ├── custom_composite_primary_key.txt │ │ │ ├── custom_primitive_type.txt │ │ │ └── full_type.txt │ │ ├── default │ │ │ ├── out_of_order_primary_key.yo.go │ │ │ └── yo_db.yo.go │ │ └── legacy_default │ │ │ ├── out_of_order_primary_key.yo.go │ │ │ └── yo_db.yo.go │ ├── testdata │ │ ├── dump_types.go.tpl │ │ └── config.yml │ └── benchmark │ │ └── benchmark_test.go ├── module │ ├── builtin │ │ ├── templates │ │ │ ├── header.go.tpl │ │ │ ├── type.go.tpl │ │ │ ├── operation.go.tpl │ │ │ ├── yo_db.go.tpl │ │ │ ├── index.go.tpl │ │ │ └── legacy_index.go.tpl │ │ ├── doc.go │ │ ├── null.go │ │ ├── builtin.go │ │ ├── builtin_test.go │ │ └── module.go │ └── module.go ├── doc.go ├── loader │ ├── doc.go │ ├── types.go │ └── util.go ├── models │ ├── doc.go │ └── model.go ├── internal │ ├── doc.go │ ├── inflector.go │ ├── inflection_rule.go │ ├── util.go │ └── inflector_test.go ├── generator │ ├── doc.go │ ├── copy.go │ ├── testdata │ │ └── empty │ │ │ └── yo_db.yo.go │ ├── templates.go │ ├── generator_test.go │ └── buffer.go ├── main.go ├── cmd │ ├── util.go │ ├── create_template.go │ └── root.go ├── config │ ├── load.go │ └── config.go ├── Makefile └── go.mod ├── .gitignore ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── release.yml ├── templates ├── yo_package.go.tpl ├── yo_db.go.tpl └── index.go.tpl ├── .goreleaser.yml ├── CONTRIBUTING.md ├── doc.go ├── LICENSE ├── loaders └── doc.go ├── models ├── doc.go └── model.go ├── tplbin └── doc.go ├── generator ├── doc.go ├── copy.go ├── buffer.go └── templates.go ├── internal ├── doc.go ├── types.go ├── util.go ├── loader_test.go ├── inflector.go ├── argtype.go └── reserved_keywords.go ├── main.go ├── test ├── testdata │ ├── custom_column_types.yml │ └── schema.sql └── testmodels │ ├── default │ ├── yo_db.yo.go │ └── outoforderprimarykey.yo.go │ ├── underscore │ ├── yo_db.yo.go │ └── out_of_order_primary_key.yo.go │ └── customtypes │ ├── yo_db.yo.go │ └── outoforderprimarykey.yo.go ├── cmd ├── create_template.go └── generate.go ├── CODE_OF_CONDUCT.md ├── go.mod ├── .circleci └── config.yml └── Makefile /v2/.gitignore: -------------------------------------------------------------------------------- 1 | /yo 2 | /vendor 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .envrc 3 | 4 | /yo 5 | /out 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please read the contribution guidelines and the CLA carefully before 2 | submitting your pull request. 3 | 4 | https://cla.developers.google.com/ 5 | -------------------------------------------------------------------------------- /v2/test/testmodels/dump_types/item.txt: -------------------------------------------------------------------------------- 1 | # Field list of Item 2 | 3 | * ID INT64 int64 4 | * Price INT64 int64 5 | 6 | # Primary Key 7 | 8 | * ID INT64 int64 9 | 10 | # Index list of Item 11 | 12 | -------------------------------------------------------------------------------- /v2/test/testmodels/dump_types/inflectionzz.txt: -------------------------------------------------------------------------------- 1 | # Field list of Inflectionzz 2 | 3 | * X STRING(32) string 4 | * Y STRING(32) string 5 | 6 | # Primary Key 7 | 8 | * X STRING(32) string 9 | 10 | # Index list of Inflectionzz 11 | 12 | -------------------------------------------------------------------------------- /v2/test/testmodels/dump_types/fereign_item.txt: -------------------------------------------------------------------------------- 1 | # Field list of FereignItem 2 | 3 | * ID INT64 int64 4 | * ItemID INT64 int64 5 | * Category INT64 int64 6 | 7 | # Primary Key 8 | 9 | * ID INT64 int64 10 | 11 | # Index list of FereignItem 12 | 13 | -------------------------------------------------------------------------------- /v2/test/testmodels/dump_types/max_length.txt: -------------------------------------------------------------------------------- 1 | # Field list of MaxLength 2 | 3 | * MaxString STRING(MAX) string 4 | * MaxBytes BYTES(MAX) []byte 5 | 6 | # Primary Key 7 | 8 | * MaxString STRING(MAX) string 9 | 10 | # Index list of MaxLength 11 | 12 | -------------------------------------------------------------------------------- /v2/test/testmodels/dump_types/snake_case.txt: -------------------------------------------------------------------------------- 1 | # Field list of SnakeCase 2 | 3 | * ID INT64 int64 4 | * StringID STRING(32) string 5 | * FooBarBaz INT64 int64 6 | 7 | # Primary Key 8 | 9 | * ID INT64 int64 10 | 11 | # Index list of SnakeCase 12 | 13 | * snake_cases_by_string_id 14 | -------------------------------------------------------------------------------- /v2/test/testmodels/dump_types/generated_column.txt: -------------------------------------------------------------------------------- 1 | # Field list of GeneratedColumn 2 | 3 | * ID INT64 int64 4 | * FirstName STRING(50) string 5 | * LastName STRING(50) string 6 | * FullName STRING(100) string 7 | 8 | # Primary Key 9 | 10 | * ID INT64 int64 11 | 12 | # Index list of GeneratedColumn 13 | 14 | -------------------------------------------------------------------------------- /templates/yo_package.go.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | // Package {{ .Package }} contains the types. 3 | package {{ .Package }} 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | 10 | "cloud.google.com/go/spanner" 11 | "google.golang.org/api/iterator" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | ) 15 | -------------------------------------------------------------------------------- /v2/test/testmodels/dump_types/out_of_order_primary_key.txt: -------------------------------------------------------------------------------- 1 | # Field list of OutOfOrderPrimaryKey 2 | 3 | * PKey1 STRING(32) string 4 | * PKey2 STRING(32) string 5 | * PKey3 STRING(32) string 6 | 7 | # Primary Key 8 | 9 | * PKey2 STRING(32) string 10 | * PKey1 STRING(32) string 11 | * PKey3 STRING(32) string 12 | 13 | # Index list of OutOfOrderPrimaryKey 14 | 15 | -------------------------------------------------------------------------------- /v2/test/testdata/dump_types.go.tpl: -------------------------------------------------------------------------------- 1 | # Field list of {{ .Name }} 2 | 3 | {{ range .Fields -}} 4 | * {{ .Name }} {{ .SpannerDataType }} {{ .Type }} 5 | {{ end }} 6 | # Primary Key 7 | 8 | {{ range .PrimaryKeyFields -}} 9 | * {{ .Name }} {{ .SpannerDataType }} {{ .Type }} 10 | {{ end }} 11 | # Index list of {{ .Name }} 12 | 13 | {{ range .Indexes -}} 14 | * {{ .IndexName }} 15 | {{ end -}} 16 | -------------------------------------------------------------------------------- /v2/module/builtin/templates/header.go.tpl: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | 3 | {{if .BuildTag -}} 4 | // +build {{ .BuildTag}} 5 | {{- end -}} 6 | 7 | // Package {{ .Package }} contains the types. 8 | package {{ .Package }} 9 | 10 | import ( 11 | "context" 12 | "errors" 13 | "fmt" 14 | 15 | "cloud.google.com/go/spanner" 16 | "google.golang.org/api/iterator" 17 | "google.golang.org/grpc/codes" 18 | "google.golang.org/grpc/status" 19 | ) 20 | -------------------------------------------------------------------------------- /v2/test/testmodels/dump_types/composite_primary_key.txt: -------------------------------------------------------------------------------- 1 | # Field list of CompositePrimaryKey 2 | 3 | * ID INT64 int64 4 | * PKey1 STRING(32) string 5 | * PKey2 INT64 int64 6 | * Error INT64 int64 7 | * X STRING(32) string 8 | * Y STRING(32) string 9 | * Z STRING(32) string 10 | 11 | # Primary Key 12 | 13 | * PKey1 STRING(32) string 14 | * PKey2 INT64 int64 15 | 16 | # Index list of CompositePrimaryKey 17 | 18 | * CompositePrimaryKeysByError 19 | * CompositePrimaryKeysByError2 20 | * CompositePrimaryKeysByError3 21 | * CompositePrimaryKeysByXY 22 | -------------------------------------------------------------------------------- /v2/test/testmodels/dump_types/custom_composite_primary_key.txt: -------------------------------------------------------------------------------- 1 | # Field list of CustomCompositePrimaryKey 2 | 3 | * ID INT64 int64 4 | * PKey1 STRING(32) string 5 | * PKey2 INT64 int64 6 | * Error INT64 int64 7 | * X STRING(32) string 8 | * Y STRING(32) string 9 | * Z STRING(32) string 10 | 11 | # Primary Key 12 | 13 | * PKey1 STRING(32) string 14 | * PKey2 INT64 int64 15 | 16 | # Index list of CustomCompositePrimaryKey 17 | 18 | * CustomCompositePrimaryKeysByError 19 | * CustomCompositePrimaryKeysByError2 20 | * CustomCompositePrimaryKeysByError3 21 | * CustomCompositePrimaryKeysByXY 22 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | - name: Set up Go 1.24 15 | uses: actions/setup-go@v3 16 | with: 17 | go-version: 1.24.x 18 | - name: Run GoReleaser 19 | uses: goreleaser/goreleaser-action@v2 20 | with: 21 | version: latest 22 | args: release --clean 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | before: 3 | hooks: 4 | - go mod download 5 | builds: 6 | - main: . 7 | binary: yo 8 | env: 9 | - CGO_ENABLED=0 10 | goos: 11 | - darwin 12 | - linux 13 | - windows 14 | goarch: 15 | - amd64 16 | - arm64 17 | - arm 18 | - 386 19 | ldflags: 20 | - -s -w -X go.mercari.io/yo/cmd.version={{.Version}} 21 | ignore: 22 | - goos: darwin 23 | goarch: 386 24 | 25 | archives: 26 | - formats: tar.gz 27 | wrap_in_directory: true 28 | format_overrides: 29 | - goos: windows 30 | formats: zip 31 | name_template: '{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' 32 | files: 33 | - LICENSE 34 | - README.md 35 | 36 | checksum: 37 | name_template: 'checksums.txt' 38 | 39 | snapshot: 40 | version_template: "SNAPSHOT-{{ .Tag }}" 41 | 42 | changelog: 43 | sort: asc 44 | filters: 45 | exclude: 46 | - '^test:' 47 | - 'README' 48 | - Merge pull request 49 | - Merge branch 50 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google/conduct/). 29 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package main // import "go.mercari.io/yo" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2016 Kenneth Shaw 2 | Copyright (c) 2020 Mercari, Inc. 3 | Copyright (c) 2020 Google LLC 4 | 5 | MIT License 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | "Software"), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /v2/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package main // import "go.mercari.io/yo/v2" 21 | -------------------------------------------------------------------------------- /loaders/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package loaders // import "go.mercari.io/yo/loaders" 21 | -------------------------------------------------------------------------------- /models/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package models // import "go.mercari.io/yo/models" 21 | -------------------------------------------------------------------------------- /tplbin/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package tplbin // import "go.mercari.io/yo/tplbin" 21 | -------------------------------------------------------------------------------- /generator/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package generator // import "go.mercari.io/yo/generator" 21 | -------------------------------------------------------------------------------- /internal/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package internal // import "go.mercari.io/yo/internal" 21 | -------------------------------------------------------------------------------- /v2/loader/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package loader // import "go.mercari.io/yo/v2/loader" 21 | -------------------------------------------------------------------------------- /v2/models/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package models // import "go.mercari.io/yo/v2/models" 21 | -------------------------------------------------------------------------------- /v2/internal/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package internal // import "go.mercari.io/yo/v2/internal" 21 | -------------------------------------------------------------------------------- /v2/generator/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package generator // import "go.mercari.io/yo/v2/generator" 21 | -------------------------------------------------------------------------------- /v2/module/builtin/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package builtin // import "go.mercari.io/yo/v2/module/builtin" 21 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | "os" 25 | 26 | "go.mercari.io/yo/cmd" 27 | ) 28 | 29 | func main() { 30 | if err := cmd.Execute(); err != nil { 31 | fmt.Fprintf(os.Stderr, "%v\n", err) 32 | os.Exit(1) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /v2/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package main 21 | 22 | import ( 23 | "fmt" 24 | "os" 25 | 26 | "go.mercari.io/yo/v2/cmd" 27 | ) 28 | 29 | func main() { 30 | if err := cmd.Execute(); err != nil { 31 | fmt.Fprintf(os.Stderr, "%v\n", err) 32 | os.Exit(1) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/testdata/custom_column_types.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Mercari, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | # this software and associated documentation files (the "Software"), to deal in 5 | # the Software without restriction, including without limitation the rights to 6 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | # the Software, and to permit persons to whom the Software is furnished to do so, 8 | # subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | tables: 21 | - name: "CompositePrimaryKeys" 22 | columns: 23 | Id: "uint64" 24 | PKey2: "uint32" 25 | Error: "int8" 26 | - name: "FullTypes" 27 | columns: 28 | FTInt: "int32" 29 | FTFloat: "float32" 30 | -------------------------------------------------------------------------------- /v2/test/testmodels/dump_types/custom_primitive_type.txt: -------------------------------------------------------------------------------- 1 | # Field list of CustomPrimitiveType 2 | 3 | * PKey STRING(32) string 4 | * FTInt64 INT64 int64 5 | * FTInt64null INT64 spanner.NullInt64 6 | * FTInt32 INT64 int64 7 | * FTInt32null INT64 spanner.NullInt64 8 | * FTInt16 INT64 int64 9 | * FTInt16null INT64 spanner.NullInt64 10 | * FTInt8 INT64 int64 11 | * FTInt8null INT64 spanner.NullInt64 12 | * FTUInt64 INT64 int64 13 | * FTUInt64null INT64 spanner.NullInt64 14 | * FTUInt32 INT64 int64 15 | * FTUInt32null INT64 spanner.NullInt64 16 | * FTUInt16 INT64 int64 17 | * FTUInt16null INT64 spanner.NullInt64 18 | * FTUInt8 INT64 int64 19 | * FTUInt8null INT64 spanner.NullInt64 20 | * FTArrayInt64 ARRAY []int64 21 | * FTArrayInt64null ARRAY []int64 22 | * FTArrayInt32 ARRAY []int64 23 | * FTArrayInt32null ARRAY []int64 24 | * FTArrayInt16 ARRAY []int64 25 | * FTArrayInt16null ARRAY []int64 26 | * FTArrayInt8 ARRAY []int64 27 | * FTArrayInt8null ARRAY []int64 28 | * FTArrayUINt64 ARRAY []int64 29 | * FTArrayUINt64null ARRAY []int64 30 | * FTArrayUINt32 ARRAY []int64 31 | * FTArrayUINt32null ARRAY []int64 32 | * FTArrayUINt16 ARRAY []int64 33 | * FTArrayUINt16null ARRAY []int64 34 | * FTArrayUINt8 ARRAY []int64 35 | * FTArrayUINt8null ARRAY []int64 36 | 37 | # Primary Key 38 | 39 | * PKey STRING(32) string 40 | 41 | # Index list of CustomPrimitiveType 42 | 43 | -------------------------------------------------------------------------------- /v2/test/testmodels/dump_types/full_type.txt: -------------------------------------------------------------------------------- 1 | # Field list of FullType 2 | 3 | * PKey STRING(32) string 4 | * FTString STRING(32) string 5 | * FTStringNull STRING(32) spanner.NullString 6 | * FTBool BOOL bool 7 | * FTBoolNull BOOL spanner.NullBool 8 | * FTBytes BYTES(32) []byte 9 | * FTBytesNull BYTES(32) []byte 10 | * FTTimestamp TIMESTAMP time.Time 11 | * FTTimestampNull TIMESTAMP spanner.NullTime 12 | * FTInt INT64 int64 13 | * FTIntNull INT64 spanner.NullInt64 14 | * FTFloat FLOAT64 float64 15 | * FTFloatNull FLOAT64 spanner.NullFloat64 16 | * FTDate DATE civil.Date 17 | * FTDateNull DATE spanner.NullDate 18 | * FTJSON JSON spanner.NullJSON 19 | * FTJSONNull JSON spanner.NullJSON 20 | * FTArrayStringNull ARRAY []string 21 | * FTArrayString ARRAY []string 22 | * FTArrayBoolNull ARRAY []bool 23 | * FTArrayBool ARRAY []bool 24 | * FTArrayBytesNull ARRAY [][]byte 25 | * FTArrayBytes ARRAY [][]byte 26 | * FTArrayTimestampNull ARRAY []time.Time 27 | * FTArrayTimestamp ARRAY []time.Time 28 | * FTArrayIntNull ARRAY []int64 29 | * FTArrayInt ARRAY []int64 30 | * FTArrayFloatNull ARRAY []float64 31 | * FTArrayFloat ARRAY []float64 32 | * FTArrayDateNull ARRAY []civil.Date 33 | * FTArrayDate ARRAY []civil.Date 34 | * FTArrayJSONNull ARRAY []spanner.NullJSON 35 | * FTArrayJSON ARRAY []spanner.NullJSON 36 | 37 | # Primary Key 38 | 39 | * PKey STRING(32) string 40 | 41 | # Index list of FullType 42 | 43 | * FullTypesByFTString 44 | * FullTypesByInTimestampNull 45 | * FullTypesByIntDate 46 | * FullTypesByIntTimestamp 47 | * FullTypesByTimestamp 48 | -------------------------------------------------------------------------------- /v2/cmd/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package cmd 21 | 22 | import ( 23 | "context" 24 | "fmt" 25 | 26 | "cloud.google.com/go/spanner" 27 | ) 28 | 29 | func connectSpanner(ctx context.Context, project, instance, database string) (*spanner.Client, error) { 30 | databaseName := fmt.Sprintf("projects/%s/instances/%s/databases/%s", 31 | project, instance, database) 32 | spannerClient, err := spanner.NewClient(ctx, databaseName) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | return spannerClient, nil 38 | } 39 | -------------------------------------------------------------------------------- /v2/module/builtin/null.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package builtin 21 | 22 | import ( 23 | "go.mercari.io/yo/v2/module" 24 | ) 25 | 26 | type nullMod struct { 27 | typ module.ModuleType 28 | } 29 | 30 | func newNullModule(typ module.ModuleType) module.Module { 31 | return &nullMod{ 32 | typ: typ, 33 | } 34 | } 35 | 36 | func (m *nullMod) Name() string { 37 | return "null" 38 | } 39 | 40 | func (m *nullMod) Type() module.ModuleType { 41 | return m.typ 42 | } 43 | 44 | func (m *nullMod) Load() ([]byte, error) { 45 | return nil, nil 46 | } 47 | -------------------------------------------------------------------------------- /v2/config/load.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package config 21 | 22 | import ( 23 | "fmt" 24 | "os" 25 | 26 | "gopkg.in/yaml.v2" 27 | ) 28 | 29 | func Load(path string) (*Config, error) { 30 | file, err := os.Open(path) 31 | if err != nil { 32 | if os.IsNotExist(err) { 33 | return &Config{}, nil 34 | } 35 | return nil, fmt.Errorf("failed to read config file: %v", err) 36 | } 37 | defer file.Close() 38 | 39 | var cfg Config 40 | if err := yaml.NewDecoder(file).Decode(&cfg); err != nil { 41 | return nil, fmt.Errorf("failed to decode config file: %v", err) 42 | } 43 | 44 | return &cfg, nil 45 | } 46 | -------------------------------------------------------------------------------- /v2/config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package config 21 | 22 | type Config struct { 23 | Tables []Table `yaml:"tables"` 24 | Inflections []Inflection `yaml:"inflections"` 25 | } 26 | 27 | // Table represents custom type definitions 28 | type Table struct { 29 | Name string `yaml:"name"` 30 | Columns []Column `yaml:"columns"` 31 | } 32 | 33 | // Column represents custom type definitions 34 | type Column struct { 35 | Name string `yaml:"name"` 36 | CustomType string `yaml:"customType"` 37 | } 38 | 39 | type Inflection struct { 40 | Singular string `yaml:"singular"` 41 | Plural string `yaml:"plural"` 42 | } 43 | -------------------------------------------------------------------------------- /cmd/create_template.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package cmd 21 | 22 | import ( 23 | "github.com/spf13/cobra" 24 | "go.mercari.io/yo/generator" 25 | ) 26 | 27 | var createTemplatePath string 28 | 29 | var createTemplateCmd = &cobra.Command{ 30 | Use: "create-template", 31 | Short: "yo create-template generates default template files ", 32 | Example: `yo create-template --template-path templates`, 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | return generator.CopyDefaultTemplates(createTemplatePath) 35 | }, 36 | } 37 | 38 | func init() { 39 | createTemplateCmd.Flags().StringVar(&createTemplatePath, "template-path", "", "destination template path") 40 | rootCmd.AddCommand(createTemplateCmd) 41 | } 42 | -------------------------------------------------------------------------------- /v2/cmd/create_template.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package cmd 21 | 22 | import ( 23 | "github.com/spf13/cobra" 24 | "go.mercari.io/yo/v2/generator" 25 | ) 26 | 27 | var createTemplatePath string 28 | 29 | var createTemplateCmd = &cobra.Command{ 30 | Use: "create-template", 31 | Short: "yo create-template generates default template files ", 32 | Example: `yo create-template --template-path templates`, 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | return generator.CopyDefaultTemplates(createTemplatePath) 35 | }, 36 | } 37 | 38 | func init() { 39 | createTemplateCmd.Flags().StringVar(&createTemplatePath, "template-path", "", "destination template path") 40 | rootCmd.AddCommand(createTemplateCmd) 41 | } 42 | -------------------------------------------------------------------------------- /generator/copy.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package generator 21 | 22 | import ( 23 | "io" 24 | "os" 25 | "path/filepath" 26 | 27 | "go.mercari.io/yo/tplbin" 28 | ) 29 | 30 | // CopyDefaultTemplates copies default templete files to dir. 31 | func CopyDefaultTemplates(dir string) error { 32 | for _, tf := range tplbin.Assets.Files { 33 | if err := func() (err error) { 34 | file, err := os.OpenFile(filepath.Join(dir, tf.Name()), os.O_RDWR|os.O_CREATE, 0666) 35 | if err != nil { 36 | return err 37 | } 38 | defer func() { 39 | if cerr := file.Close(); err == nil { 40 | err = cerr 41 | } 42 | }() 43 | 44 | _, err = io.Copy(file, tf) 45 | return 46 | }(); err != nil { 47 | return err 48 | } 49 | } 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /v2/module/builtin/builtin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package builtin 21 | 22 | import ( 23 | "go.mercari.io/yo/v2/module" 24 | ) 25 | 26 | var ( 27 | Header = newBuiltin(module.HeaderModule, "header") 28 | Type = newBuiltin(module.TypeModule, "type") 29 | Operation = newBuiltin(module.TypeModule, "operation") 30 | Index = newBuiltin(module.TypeModule, "index") 31 | LegacyIndex = newBuiltin(module.TypeModule, "legacy_index") 32 | Interface = newBuiltin(module.GlobalModule, "yo_db") 33 | ) 34 | 35 | var All = []module.Module{ 36 | Header, 37 | Type, 38 | Operation, 39 | Index, 40 | LegacyIndex, 41 | Interface, 42 | } 43 | 44 | var ( 45 | NullHeader = newNullModule(module.HeaderModule) 46 | NullGlobal = newNullModule(module.GlobalModule) 47 | NullType = newNullModule(module.TypeModule) 48 | ) 49 | -------------------------------------------------------------------------------- /v2/module/builtin/builtin_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package builtin 21 | 22 | import ( 23 | "testing" 24 | 25 | "go.mercari.io/yo/v2/module" 26 | ) 27 | 28 | func TestBuiltin(t *testing.T) { 29 | for _, m := range All { 30 | b, err := m.Load() 31 | if err != nil { 32 | t.Fatalf("failed to load module %q: %v", m.Name(), err) 33 | } 34 | 35 | if len(b) == 0 { 36 | t.Errorf("there is no contents in module %q", m.Name()) 37 | } 38 | } 39 | } 40 | 41 | func TestNullModule(t *testing.T) { 42 | nullModules := []module.Module{ 43 | NullHeader, 44 | NullGlobal, 45 | NullType, 46 | } 47 | 48 | for _, m := range nullModules { 49 | b, err := m.Load() 50 | if err != nil { 51 | t.Fatalf("failed to load module %q: %v", m.Name(), err) 52 | } 53 | 54 | if len(b) != 0 { 55 | t.Errorf("null module must have no contents %q: %s", m.Type(), b) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /internal/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package internal 21 | 22 | import "go.mercari.io/yo/models" 23 | 24 | // Field contains field information. 25 | type Field struct { 26 | Name string 27 | Type string 28 | CustomType string 29 | NilType string 30 | Len int 31 | Col *models.Column 32 | } 33 | 34 | // Type is a template item for a type. 35 | type Type struct { 36 | Name string 37 | Schema string 38 | PrimaryKey *Field 39 | PrimaryKeyFields []*Field 40 | Fields []*Field 41 | Table *models.Table 42 | Indexes []*Index 43 | } 44 | 45 | // Index is a template item for a index into a table. 46 | type Index struct { 47 | FuncName string 48 | Schema string 49 | Type *Type 50 | Fields []*Field 51 | StoringFields []*Field 52 | NullableFields []*Field 53 | Index *models.Index 54 | } 55 | -------------------------------------------------------------------------------- /v2/generator/copy.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package generator 21 | 22 | import ( 23 | "fmt" 24 | "os" 25 | "path/filepath" 26 | 27 | "go.mercari.io/yo/v2/module/builtin" 28 | ) 29 | 30 | // CopyDefaultTemplates copies default templete files to dir. 31 | func CopyDefaultTemplates(dir string) error { 32 | for _, m := range builtin.All { 33 | if err := func() (err error) { 34 | filename := fmt.Sprintf("%s.go.tpl", m.Name()) 35 | file, err := os.OpenFile(filepath.Join(dir, filename), os.O_RDWR|os.O_CREATE, 0666) 36 | if err != nil { 37 | return err 38 | } 39 | defer func() { 40 | if cerr := file.Close(); err == nil { 41 | err = cerr 42 | } 43 | }() 44 | 45 | b, err := m.Load() 46 | if err != nil { 47 | return fmt.Errorf("failed to load builtin module %q: %v", m.Name(), err) 48 | } 49 | 50 | _, err = file.Write(b) 51 | return 52 | }(); err != nil { 53 | return err 54 | } 55 | } 56 | return nil 57 | } 58 | -------------------------------------------------------------------------------- /generator/buffer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package generator 21 | 22 | import ( 23 | "bytes" 24 | "strings" 25 | ) 26 | 27 | // TBuf is to hold the executed templates. 28 | type TBuf struct { 29 | TemplateType TemplateType 30 | Name string 31 | Subname string 32 | Buf *bytes.Buffer 33 | } 34 | 35 | // TBufSlice is a slice of TBuf compatible with sort.Interface. 36 | type TBufSlice []TBuf 37 | 38 | func (t TBufSlice) Len() int { 39 | return len(t) 40 | } 41 | 42 | func (t TBufSlice) Swap(i, j int) { 43 | t[i], t[j] = t[j], t[i] 44 | } 45 | 46 | func (t TBufSlice) Less(i, j int) bool { 47 | if t[i].TemplateType < t[j].TemplateType { 48 | return true 49 | } else if t[j].TemplateType < t[i].TemplateType { 50 | return false 51 | } 52 | 53 | if strings.Compare(t[i].Name, t[j].Name) < 0 { 54 | return true 55 | } else if strings.Compare(t[j].Name, t[i].Name) < 0 { 56 | return false 57 | } 58 | 59 | return strings.Compare(t[i].Subname, t[j].Subname) < 0 60 | } 61 | -------------------------------------------------------------------------------- /v2/module/module.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package module 21 | 22 | import ( 23 | "fmt" 24 | "os" 25 | ) 26 | 27 | // ModuleType represents a module type. 28 | type ModuleType uint 29 | 30 | const ( 31 | GlobalModule ModuleType = iota 32 | TypeModule 33 | HeaderModule 34 | ) 35 | 36 | type Module interface { 37 | Type() ModuleType 38 | Name() string 39 | Load() ([]byte, error) 40 | } 41 | 42 | type module struct { 43 | typ ModuleType 44 | name string 45 | path string 46 | } 47 | 48 | func New(typ ModuleType, name string, path string) Module { 49 | return &module{ 50 | typ: typ, 51 | name: name, 52 | path: path, 53 | } 54 | } 55 | 56 | func (m *module) Name() string { 57 | return m.name 58 | } 59 | 60 | func (m *module) Type() ModuleType { 61 | return m.typ 62 | } 63 | 64 | func (m *module) Load() ([]byte, error) { 65 | b, err := os.ReadFile(m.path) 66 | if err != nil { 67 | return nil, fmt.Errorf("failed to read file %s: %w", m.path, err) 68 | } 69 | 70 | return b, nil 71 | } 72 | -------------------------------------------------------------------------------- /v2/module/builtin/module.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package builtin 21 | 22 | import ( 23 | "embed" 24 | "fmt" 25 | "io" 26 | 27 | "go.mercari.io/yo/v2/module" 28 | ) 29 | 30 | //go:embed templates/*.tpl 31 | var templates embed.FS 32 | 33 | type builtinMod struct { 34 | typ module.ModuleType 35 | name string 36 | } 37 | 38 | func newBuiltin(typ module.ModuleType, name string) module.Module { 39 | return &builtinMod{ 40 | typ: typ, 41 | name: name, 42 | } 43 | } 44 | 45 | func (m *builtinMod) Name() string { 46 | return m.name 47 | } 48 | 49 | func (m *builtinMod) Type() module.ModuleType { 50 | return m.typ 51 | } 52 | 53 | func (m *builtinMod) Load() ([]byte, error) { 54 | f, err := templates.Open(fmt.Sprintf("templates/%s.go.tpl", m.name)) 55 | if err != nil { 56 | return nil, fmt.Errorf("open %s: %w", m.name, err) 57 | } 58 | 59 | defer f.Close() 60 | 61 | b, err := io.ReadAll(f) 62 | if err != nil { 63 | return nil, fmt.Errorf("failed to read file from assets: %w", err) 64 | } 65 | 66 | return b, nil 67 | } 68 | -------------------------------------------------------------------------------- /v2/loader/types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package loader 21 | 22 | // SpannerTable represents table info. 23 | type SpannerTable struct { 24 | TableName string // table_name 25 | ParentTableName string 26 | } 27 | 28 | // SpannerColumn represents column info. 29 | type SpannerColumn struct { 30 | FieldOrdinal int // field_ordinal 31 | ColumnName string // column_name 32 | DataType string // data_type 33 | NotNull bool // not_null 34 | IsPrimaryKey bool // is_primary_key 35 | IsGenerated bool // is_generated 36 | IsHidden bool // is_hidden 37 | } 38 | 39 | // SpannerIndex represents an index. 40 | type SpannerIndex struct { 41 | IndexName string // index name 42 | IsUnique bool // the index is unique ro not 43 | IsPrimary bool // the index is primary key or not 44 | } 45 | 46 | // SpannerIndexColumn represents index column info. 47 | type SpannerIndexColumn struct { 48 | SeqNo int // seq_no. If is'a Storing Column, this value is 0. 49 | ColumnName string // column_name 50 | Storing bool // storing column or not 51 | } 52 | -------------------------------------------------------------------------------- /templates/yo_db.go.tpl: -------------------------------------------------------------------------------- 1 | // YODB is the common interface for database operations. 2 | type YODB interface { 3 | YORODB 4 | } 5 | 6 | // YORODB is the common interface for database operations. 7 | type YORODB interface { 8 | ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error) 9 | Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator 10 | ReadUsingIndex(ctx context.Context, table, index string, keys spanner.KeySet, columns []string) (ri *spanner.RowIterator) 11 | Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator 12 | } 13 | 14 | // YOLog provides the log func used by generated queries. 15 | var YOLog = func(context.Context, string, ...interface{}) { } 16 | 17 | func newError(method, table string, err error) error { 18 | code := spanner.ErrCode(err) 19 | return newErrorWithCode(code, method, table, err) 20 | } 21 | 22 | func newErrorWithCode(code codes.Code, method, table string, err error) error { 23 | return &yoError{ 24 | method: method, 25 | table: table, 26 | err: err, 27 | code: code, 28 | } 29 | } 30 | 31 | type yoError struct { 32 | err error 33 | method string 34 | table string 35 | code codes.Code 36 | } 37 | 38 | func (e yoError) Error() string { 39 | return fmt.Sprintf("yo error in %s(%s): %v", e.method, e.table, e.err) 40 | } 41 | 42 | func (e yoError) Unwrap() error { 43 | return e.err 44 | } 45 | 46 | func (e yoError) DBTableName() string { 47 | return e.table 48 | } 49 | 50 | // GRPCStatus implements a conversion to a gRPC status using `status.Convert(error)`. 51 | // If the error is originated from the Spanner library, this returns a gRPC status of 52 | // the original error. It may contain details of the status such as RetryInfo. 53 | func (e yoError) GRPCStatus() *status.Status { 54 | var ae *apierror.APIError 55 | if errors.As(e.err, &ae) { 56 | return status.Convert(ae) 57 | } 58 | 59 | return status.New(e.code, e.Error()) 60 | } 61 | 62 | func (e yoError) Timeout() bool { return e.code == codes.DeadlineExceeded } 63 | func (e yoError) Temporary() bool { return e.code == codes.DeadlineExceeded } 64 | func (e yoError) NotFound() bool { return e.code == codes.NotFound } 65 | -------------------------------------------------------------------------------- /v2/internal/inflector.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package internal 21 | 22 | import ( 23 | "github.com/jinzhu/inflection" 24 | "go.mercari.io/yo/v2/config" 25 | ) 26 | 27 | type Inflector interface { 28 | Singularize(string) string 29 | Pluralize(string) string 30 | } 31 | 32 | type inflector struct{} 33 | 34 | func (i *inflector) Singularize(s string) string { 35 | return inflection.Singular(s) 36 | } 37 | func (i *inflector) Pluralize(s string) string { 38 | return inflection.Plural(s) 39 | } 40 | 41 | func NewInflector(rules []config.Inflection) (Inflector, error) { 42 | if err := registerRule(rules); err != nil { 43 | return nil, err 44 | } 45 | return &inflector{}, nil 46 | } 47 | 48 | func registerRule(rules []config.Inflection) error { 49 | for _, rule := range rules { 50 | inflection.AddIrregular(rule.Singular, rule.Plural) 51 | } 52 | 53 | for _, rule := range defaultSingularInflections { 54 | inflection.AddSingular(rule.find, rule.replace) 55 | } 56 | for _, rule := range defaultPluralInflections { 57 | inflection.AddPlural(rule.find, rule.replace) 58 | } 59 | for _, rule := range defaultIrregularRules { 60 | inflection.AddIrregular(rule.singlar, rule.plural) 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /v2/cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package cmd 21 | 22 | import ( 23 | "runtime/debug" 24 | "strings" 25 | 26 | "github.com/spf13/cobra" 27 | ) 28 | 29 | const ( 30 | defaultSuffix = ".yo.go" 31 | exampleUsage = ` 32 | # Generate models from ddl under models directory 33 | yo generate schema.sql --from-ddl -o models 34 | 35 | # Generate models under models directory 36 | yo generate $SPANNER_PROJECT_NAME $SPANNER_INSTANCE_NAME $SPANNER_DATABASE_NAME -o models 37 | ` 38 | ) 39 | 40 | var version string 41 | 42 | var ( 43 | rootCmd = &cobra.Command{ 44 | Use: "yo", 45 | Short: "yo is a command-line tool to generate Go code for Google Cloud Spanner.", 46 | Args: func(cmd *cobra.Command, args []string) error { 47 | return nil 48 | }, 49 | Example: strings.Trim(exampleUsage, "\n"), 50 | RunE: nil, 51 | SilenceUsage: true, 52 | SilenceErrors: true, 53 | Version: versionInfo(), 54 | } 55 | ) 56 | 57 | func Execute() error { 58 | return rootCmd.Execute() 59 | } 60 | 61 | func versionInfo() string { 62 | if version != "" { 63 | return version 64 | } 65 | 66 | // For those who "go install" yo 67 | info, ok := debug.ReadBuildInfo() 68 | if !ok { 69 | return "(devel)" 70 | } 71 | return info.Main.Version 72 | } 73 | -------------------------------------------------------------------------------- /internal/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package internal 21 | 22 | import ( 23 | "fmt" 24 | "strings" 25 | 26 | "github.com/kenshaw/snaker" 27 | ) 28 | 29 | // reverseIndexRune finds the last rune r in s, returning -1 if not present. 30 | func reverseIndexRune(s string, r rune) int { 31 | if s == "" { 32 | return -1 33 | } 34 | 35 | rs := []rune(s) 36 | for i := len(rs) - 1; i >= 0; i-- { 37 | if rs[i] == r { 38 | return i 39 | } 40 | } 41 | 42 | return -1 43 | } 44 | 45 | // SinguralizeIdentifier will singularize a identifier, returning it in 46 | // CamelCase. 47 | func SingularizeIdentifier(in Inflector, s string) string { 48 | if i := reverseIndexRune(s, '_'); i != -1 { 49 | s = s[:i] + "_" + in.Singularize(s[i+1:]) 50 | } else { 51 | s = in.Singularize(s) 52 | } 53 | 54 | // return snaker.SnakeToCamelIdentifier(s) 55 | return snaker.ForceCamelIdentifier(s) 56 | } 57 | 58 | // EscapeColumnName will escape a column name if using reserved keyword as column name, returning it in 59 | // surrounded backquotes. 60 | func EscapeColumnName(s string) string { 61 | if _, ok := reservedKeywords[strings.ToUpper(s)]; ok { 62 | // return surrounded s with backquotes if reserved keyword 63 | return fmt.Sprintf("`%s`", s) 64 | } 65 | // return s if not reserved keyword 66 | return s 67 | } 68 | -------------------------------------------------------------------------------- /v2/test/testdata/config.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Mercari, Inc. 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | # this software and associated documentation files (the "Software"), to deal in 5 | # the Software without restriction, including without limitation the rights to 6 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | # the Software, and to permit persons to whom the Software is furnished to do so, 8 | # subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | inflections: 21 | - singular: inflection 22 | plural: inflectionzz 23 | tables: 24 | - name: "CustomCompositePrimaryKeys" 25 | columns: 26 | - name: Id 27 | customType: "uint64" 28 | - name: PKey2 29 | customType: "uint32" 30 | - name: Error 31 | customType: "int8" 32 | - name: "CustomPrimitiveTypes" 33 | columns: 34 | - name: FTInt64 35 | customType: "int64" 36 | - name: FTInt64Null 37 | customType: "int64" 38 | - name: FTInt32 39 | customType: "int32" 40 | - name: FTInt32Null 41 | customType: "int32" 42 | - name: FTInt16 43 | customType: "int16" 44 | - name: FTInt16Null 45 | customType: "int16" 46 | - name: FTInt8 47 | customType: "int8" 48 | - name: FTInt8Null 49 | customType: "int8" 50 | - name: FTUInt64 51 | customType: "uint64" 52 | - name: FTUInt64Null 53 | customType: "uint64" 54 | - name: FTUInt32 55 | customType: "uint32" 56 | - name: FTUInt32Null 57 | customType: "uint32" 58 | - name: FTUInt16 59 | customType: "uint16" 60 | - name: FTUInt16Null 61 | customType: "uint16" 62 | - name: FTUInt8 63 | customType: "uint8" 64 | - name: FTUInt8Null 65 | customType: "uint8" 66 | -------------------------------------------------------------------------------- /v2/generator/testdata/empty/yo_db.yo.go: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | 3 | // Package yotest contains the types. 4 | package yotest 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "fmt" 10 | 11 | "cloud.google.com/go/spanner" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | ) 15 | 16 | // YODB is the common interface for database operations. 17 | type YODB interface { 18 | YORODB 19 | } 20 | 21 | // YORODB is the common interface for database operations. 22 | type YORODB interface { 23 | ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error) 24 | Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator 25 | ReadUsingIndex(ctx context.Context, table, index string, keys spanner.KeySet, columns []string) (ri *spanner.RowIterator) 26 | Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator 27 | } 28 | 29 | // YOLog provides the log func used by generated queries. 30 | var YOLog = func(context.Context, string, ...interface{}) {} 31 | 32 | func newError(method, table string, err error) error { 33 | code := spanner.ErrCode(err) 34 | return newErrorWithCode(code, method, table, err) 35 | } 36 | 37 | func newErrorWithCode(code codes.Code, method, table string, err error) error { 38 | return &yoError{ 39 | method: method, 40 | table: table, 41 | err: err, 42 | code: code, 43 | } 44 | } 45 | 46 | type yoError struct { 47 | err error 48 | method string 49 | table string 50 | code codes.Code 51 | } 52 | 53 | func (e yoError) Error() string { 54 | return fmt.Sprintf("yo error in %s(%s): %v", e.method, e.table, e.err) 55 | } 56 | 57 | func (e yoError) Unwrap() error { 58 | return e.err 59 | } 60 | 61 | func (e yoError) DBTableName() string { 62 | return e.table 63 | } 64 | 65 | // GRPCStatus implements a conversion to a gRPC status using `status.Convert(error)`. 66 | // If the error is originated from the Spanner library, this returns a gRPC status of 67 | // the original error. It may contain details of the status such as RetryInfo. 68 | func (e yoError) GRPCStatus() *status.Status { 69 | var se *spanner.Error 70 | if errors.As(e.err, &se) { 71 | return status.Convert(se.Unwrap()) 72 | } 73 | 74 | return status.New(e.code, e.Error()) 75 | } 76 | 77 | func (e yoError) Timeout() bool { return e.code == codes.DeadlineExceeded } 78 | func (e yoError) Temporary() bool { return e.code == codes.DeadlineExceeded } 79 | func (e yoError) NotFound() bool { return e.code == codes.NotFound } 80 | -------------------------------------------------------------------------------- /test/testmodels/default/yo_db.yo.go: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | // Package models contains the types. 3 | package models 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | 10 | "cloud.google.com/go/spanner" 11 | "github.com/googleapis/gax-go/v2/apierror" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | ) 15 | 16 | // YODB is the common interface for database operations. 17 | type YODB interface { 18 | YORODB 19 | } 20 | 21 | // YORODB is the common interface for database operations. 22 | type YORODB interface { 23 | ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error) 24 | Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator 25 | ReadUsingIndex(ctx context.Context, table, index string, keys spanner.KeySet, columns []string) (ri *spanner.RowIterator) 26 | Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator 27 | } 28 | 29 | // YOLog provides the log func used by generated queries. 30 | var YOLog = func(context.Context, string, ...interface{}) {} 31 | 32 | func newError(method, table string, err error) error { 33 | code := spanner.ErrCode(err) 34 | return newErrorWithCode(code, method, table, err) 35 | } 36 | 37 | func newErrorWithCode(code codes.Code, method, table string, err error) error { 38 | return &yoError{ 39 | method: method, 40 | table: table, 41 | err: err, 42 | code: code, 43 | } 44 | } 45 | 46 | type yoError struct { 47 | err error 48 | method string 49 | table string 50 | code codes.Code 51 | } 52 | 53 | func (e yoError) Error() string { 54 | return fmt.Sprintf("yo error in %s(%s): %v", e.method, e.table, e.err) 55 | } 56 | 57 | func (e yoError) Unwrap() error { 58 | return e.err 59 | } 60 | 61 | func (e yoError) DBTableName() string { 62 | return e.table 63 | } 64 | 65 | // GRPCStatus implements a conversion to a gRPC status using `status.Convert(error)`. 66 | // If the error is originated from the Spanner library, this returns a gRPC status of 67 | // the original error. It may contain details of the status such as RetryInfo. 68 | func (e yoError) GRPCStatus() *status.Status { 69 | var ae *apierror.APIError 70 | if errors.As(e.err, &ae) { 71 | return status.Convert(ae) 72 | } 73 | 74 | return status.New(e.code, e.Error()) 75 | } 76 | 77 | func (e yoError) Timeout() bool { return e.code == codes.DeadlineExceeded } 78 | func (e yoError) Temporary() bool { return e.code == codes.DeadlineExceeded } 79 | func (e yoError) NotFound() bool { return e.code == codes.NotFound } 80 | -------------------------------------------------------------------------------- /test/testmodels/underscore/yo_db.yo.go: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | // Package models contains the types. 3 | package models 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | 10 | "cloud.google.com/go/spanner" 11 | "github.com/googleapis/gax-go/v2/apierror" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | ) 15 | 16 | // YODB is the common interface for database operations. 17 | type YODB interface { 18 | YORODB 19 | } 20 | 21 | // YORODB is the common interface for database operations. 22 | type YORODB interface { 23 | ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error) 24 | Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator 25 | ReadUsingIndex(ctx context.Context, table, index string, keys spanner.KeySet, columns []string) (ri *spanner.RowIterator) 26 | Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator 27 | } 28 | 29 | // YOLog provides the log func used by generated queries. 30 | var YOLog = func(context.Context, string, ...interface{}) {} 31 | 32 | func newError(method, table string, err error) error { 33 | code := spanner.ErrCode(err) 34 | return newErrorWithCode(code, method, table, err) 35 | } 36 | 37 | func newErrorWithCode(code codes.Code, method, table string, err error) error { 38 | return &yoError{ 39 | method: method, 40 | table: table, 41 | err: err, 42 | code: code, 43 | } 44 | } 45 | 46 | type yoError struct { 47 | err error 48 | method string 49 | table string 50 | code codes.Code 51 | } 52 | 53 | func (e yoError) Error() string { 54 | return fmt.Sprintf("yo error in %s(%s): %v", e.method, e.table, e.err) 55 | } 56 | 57 | func (e yoError) Unwrap() error { 58 | return e.err 59 | } 60 | 61 | func (e yoError) DBTableName() string { 62 | return e.table 63 | } 64 | 65 | // GRPCStatus implements a conversion to a gRPC status using `status.Convert(error)`. 66 | // If the error is originated from the Spanner library, this returns a gRPC status of 67 | // the original error. It may contain details of the status such as RetryInfo. 68 | func (e yoError) GRPCStatus() *status.Status { 69 | var ae *apierror.APIError 70 | if errors.As(e.err, &ae) { 71 | return status.Convert(ae) 72 | } 73 | 74 | return status.New(e.code, e.Error()) 75 | } 76 | 77 | func (e yoError) Timeout() bool { return e.code == codes.DeadlineExceeded } 78 | func (e yoError) Temporary() bool { return e.code == codes.DeadlineExceeded } 79 | func (e yoError) NotFound() bool { return e.code == codes.NotFound } 80 | -------------------------------------------------------------------------------- /test/testmodels/customtypes/yo_db.yo.go: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | // Package customtypes contains the types. 3 | package customtypes 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | 10 | "cloud.google.com/go/spanner" 11 | "github.com/googleapis/gax-go/v2/apierror" 12 | "google.golang.org/grpc/codes" 13 | "google.golang.org/grpc/status" 14 | ) 15 | 16 | // YODB is the common interface for database operations. 17 | type YODB interface { 18 | YORODB 19 | } 20 | 21 | // YORODB is the common interface for database operations. 22 | type YORODB interface { 23 | ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error) 24 | Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator 25 | ReadUsingIndex(ctx context.Context, table, index string, keys spanner.KeySet, columns []string) (ri *spanner.RowIterator) 26 | Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator 27 | } 28 | 29 | // YOLog provides the log func used by generated queries. 30 | var YOLog = func(context.Context, string, ...interface{}) {} 31 | 32 | func newError(method, table string, err error) error { 33 | code := spanner.ErrCode(err) 34 | return newErrorWithCode(code, method, table, err) 35 | } 36 | 37 | func newErrorWithCode(code codes.Code, method, table string, err error) error { 38 | return &yoError{ 39 | method: method, 40 | table: table, 41 | err: err, 42 | code: code, 43 | } 44 | } 45 | 46 | type yoError struct { 47 | err error 48 | method string 49 | table string 50 | code codes.Code 51 | } 52 | 53 | func (e yoError) Error() string { 54 | return fmt.Sprintf("yo error in %s(%s): %v", e.method, e.table, e.err) 55 | } 56 | 57 | func (e yoError) Unwrap() error { 58 | return e.err 59 | } 60 | 61 | func (e yoError) DBTableName() string { 62 | return e.table 63 | } 64 | 65 | // GRPCStatus implements a conversion to a gRPC status using `status.Convert(error)`. 66 | // If the error is originated from the Spanner library, this returns a gRPC status of 67 | // the original error. It may contain details of the status such as RetryInfo. 68 | func (e yoError) GRPCStatus() *status.Status { 69 | var ae *apierror.APIError 70 | if errors.As(e.err, &ae) { 71 | return status.Convert(ae) 72 | } 73 | 74 | return status.New(e.code, e.Error()) 75 | } 76 | 77 | func (e yoError) Timeout() bool { return e.code == codes.DeadlineExceeded } 78 | func (e yoError) Temporary() bool { return e.code == codes.DeadlineExceeded } 79 | func (e yoError) NotFound() bool { return e.code == codes.NotFound } 80 | -------------------------------------------------------------------------------- /models/model.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package models 21 | 22 | // Table represents table info. 23 | type Table struct { 24 | Type string // type 25 | TableName string // table_name 26 | ManualPk bool // manual_pk 27 | } 28 | 29 | // Column represents column info. 30 | type Column struct { 31 | FieldOrdinal int // field_ordinal 32 | ColumnName string // column_name 33 | DataType string // data_type 34 | NotNull bool // not_null 35 | IsPrimaryKey bool // is_primary_key 36 | IsGenerated bool // is_generated 37 | IsHidden bool // is_hidden 38 | IsAllowCommitTimestamp bool // is_allow_commit_timestamp 39 | } 40 | 41 | // Index represents an index. 42 | type Index struct { 43 | IndexName string // index_name 44 | IsUnique bool // is_unique 45 | IsPrimary bool // is_primary 46 | SeqNo int // seq_no 47 | Origin string // origin 48 | IsPartial bool // is_partial 49 | } 50 | 51 | // IndexColumn represents index column info. 52 | type IndexColumn struct { 53 | SeqNo int // seq_no. If is'a Storing Column, this value is 0. 54 | ColumnName string // column_name 55 | Storing bool // storing column or not 56 | } 57 | 58 | // CustomTypes represents custom type definitions 59 | type CustomTypes struct { 60 | Tables []struct { 61 | Name string `yaml:"name"` 62 | Columns map[string]string `yaml:"columns"` 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /v2/internal/inflection_rule.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package internal 21 | 22 | type inflectionRule struct { 23 | find string 24 | replace string 25 | } 26 | 27 | var defaultSingularInflections = []inflectionRule{ 28 | {`(slave)s$`, `$1`}, 29 | {`(drive)s$`, `$1`}, 30 | } 31 | 32 | var defaultPluralInflections = []inflectionRule{ 33 | {"^people$", "people"}, 34 | } 35 | 36 | type irregularRule struct { 37 | singlar string 38 | plural string 39 | } 40 | 41 | var defaultIrregularRules = []irregularRule{ 42 | {"foot", "feet"}, 43 | {"tooth", "teeth"}, 44 | {`mythos`, `mythoi`}, 45 | {`genie`, `genies`}, 46 | {`genus`, `genera`}, 47 | {`graffito`, `graffiti`}, 48 | {`mongoose`, `mongooses`}, 49 | {"goose", "geese"}, 50 | {`niche`, `niches`}, 51 | {`numen`, `numina`}, 52 | {`occiput`, `occiputs`}, 53 | {`trilby`, `trilbys`}, 54 | {`testis`, `testes`}, 55 | 56 | {`corpus`, `corpuses`}, 57 | {`octopus`, `octopuses`}, 58 | {`opus`, `opuses`}, 59 | {`atlas`, `atlases`}, 60 | 61 | {`move`, `moves`}, 62 | {`wave`, `waves`}, 63 | {`curve`, `curves`}, 64 | {"glove", "gloves"}, 65 | {`loaf`, `loaves`}, 66 | {"thief", "thieves"}, 67 | {"belief", "beliefs"}, 68 | {"chief", "chiefs"}, 69 | {"chef", "chefs"}, 70 | {`turf`, `turfs`}, 71 | {`beef`, `beefs`}, 72 | {`hoof`, `hoofs`}, 73 | {`cafe`, `cafes`}, 74 | 75 | {"photo", "photos"}, 76 | {"piano", "pianos"}, 77 | {"halo", "halos"}, 78 | {`hero`, `heroes`}, 79 | {`potato`, `potatoes`}, 80 | {`foe`, `foes`}, 81 | 82 | {`media`, `media`}, 83 | } 84 | -------------------------------------------------------------------------------- /v2/internal/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package internal 21 | 22 | import ( 23 | "fmt" 24 | "strings" 25 | 26 | "github.com/kenshaw/snaker" 27 | ) 28 | 29 | // reverseIndexRune finds the last rune r in s, returning -1 if not present. 30 | func reverseIndexRune(s string, r rune) int { 31 | if s == "" { 32 | return -1 33 | } 34 | 35 | rs := []rune(s) 36 | for i := len(rs) - 1; i >= 0; i-- { 37 | if rs[i] == r { 38 | return i 39 | } 40 | } 41 | 42 | return -1 43 | } 44 | 45 | // SinguralizeIdentifier will singularize a identifier, returning it in 46 | // CamelCase. 47 | func SingularizeIdentifier(in Inflector, s string) string { 48 | if i := reverseIndexRune(s, '_'); i != -1 { 49 | s = s[:i] + "_" + in.Singularize(s[i+1:]) 50 | } else { 51 | s = in.Singularize(s) 52 | } 53 | 54 | return snaker.ForceCamelIdentifier(s) 55 | } 56 | 57 | // SnakeToCamel converts the string to CamelCase 58 | func SnakeToCamel(s string) string { 59 | return snaker.ForceCamelIdentifier(s) 60 | } 61 | 62 | // CamelToSnake converts the string to snake_case 63 | func CamelToScake(s string) string { 64 | return snaker.CamelToSnakeIdentifier(s) 65 | } 66 | 67 | // EscapeColumnName will escape a column name if using reserved keyword as column name, returning it in 68 | // surrounded back quotes. 69 | func EscapeColumnName(s string) string { 70 | if _, ok := reservedKeywords[strings.ToUpper(s)]; ok { 71 | // return surrounded s with back quotes if reserved keyword 72 | return fmt.Sprintf("`%s`", s) 73 | } 74 | // return s if not reserved keyword 75 | return s 76 | } 77 | -------------------------------------------------------------------------------- /v2/module/builtin/templates/type.go.tpl: -------------------------------------------------------------------------------- 1 | {{- $short := (shortName .Name "err" "res" "sqlstr" "db" "YOLog") -}} 2 | {{- $table := (.TableName) -}} 3 | 4 | // {{ .Name }} represents a row from '{{ $table }}'. 5 | type {{ .Name }} struct { 6 | {{- range .Fields }} 7 | {{- if .IsHidden }} 8 | {{- else if eq (.SpannerDataType) (.ColumnName) }} 9 | {{ .Name }} string `spanner:"{{ .ColumnName }}" json:"{{ .ColumnName }}"` // {{ .ColumnName }} enum 10 | {{- else }} 11 | {{ .Name }} {{ .Type }} `spanner:"{{ .ColumnName }}" json:"{{ .ColumnName }}"` // {{ .ColumnName }} 12 | {{- end }} 13 | {{- end }} 14 | } 15 | 16 | func {{ .Name }}PrimaryKeys() []string { 17 | return []string{ 18 | {{- range .PrimaryKeyFields }} 19 | "{{ .ColumnName }}", 20 | {{- end }} 21 | } 22 | } 23 | 24 | func {{ .Name }}Columns() []string { 25 | return []string{ 26 | {{- range .Fields }} 27 | {{- if not .IsHidden }} 28 | "{{ .ColumnName }}", 29 | {{- end }} 30 | {{- end }} 31 | } 32 | } 33 | 34 | func {{ .Name }}WritableColumns() []string { 35 | return []string{ 36 | {{- range .Fields }} 37 | {{- if not .IsGenerated }} 38 | "{{ .ColumnName }}", 39 | {{- end }} 40 | {{- end }} 41 | } 42 | } 43 | 44 | func ({{ $short }} *{{ .Name }}) columnsToPtrs(cols []string) ([]interface{}, error) { 45 | ret := make([]interface{}, 0, len(cols)) 46 | for _, col := range cols { 47 | switch col { 48 | {{- range .Fields }} 49 | {{- if not .IsHidden }} 50 | case "{{ .ColumnName }}": 51 | ret = append(ret, yoDecode(&{{ $short }}.{{ .Name }})) 52 | {{- end }} 53 | {{- end }} 54 | default: 55 | return nil, fmt.Errorf("unknown column: %s", col) 56 | } 57 | } 58 | return ret, nil 59 | } 60 | 61 | func ({{ $short }} *{{ .Name }}) columnsToValues(cols []string) ([]interface{}, error) { 62 | ret := make([]interface{}, 0, len(cols)) 63 | for _, col := range cols { 64 | switch col { 65 | {{- range .Fields }} 66 | {{- if not .IsHidden }} 67 | case "{{ .ColumnName }}": 68 | ret = append(ret, yoEncode({{ $short }}.{{ .Name }})) 69 | {{- end }} 70 | {{- end }} 71 | default: 72 | return nil, fmt.Errorf("unknown column: %s", col) 73 | } 74 | } 75 | 76 | return ret, nil 77 | } 78 | 79 | // new{{ .Name }}_Decoder returns a decoder which reads a row from *spanner.Row 80 | // into {{ .Name }}. The decoder is not goroutine-safe. Don't use it concurrently. 81 | func new{{ .Name }}_Decoder(cols []string) func(*spanner.Row) (*{{ .Name }}, error) { 82 | return func(row *spanner.Row) (*{{ .Name }}, error) { 83 | var {{ $short }} {{ .Name }} 84 | ptrs, err := {{ $short }}.columnsToPtrs(cols) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | if err := row.Columns(ptrs...); err != nil { 90 | return nil, err 91 | } 92 | 93 | return &{{ $short }}, nil 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /v2/models/model.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package models 21 | 22 | // Schema contains information of all Go types. 23 | type Schema struct { 24 | Types []*Type 25 | } 26 | 27 | // Type is a Go type that represents a Spanner table. 28 | type Type struct { 29 | Name string // Go like (CamelCase) table name 30 | PrimaryKeyFields []*Field 31 | Fields []*Field 32 | Indexes []*Index 33 | TableName string 34 | Parent *Type 35 | } 36 | 37 | // Field is a field of Go type that represents a Spanner column. 38 | type Field struct { 39 | Name string // Go like (CamelCase) field name 40 | Type string // Go type specified by custom type or same to OriginalType below 41 | OriginalType string // Go type corresponding to Spanner type 42 | NullValue string // NULL value for Type 43 | Len int // Length for STRING, BYTES. -1 for MAX or other types 44 | ColumnName string // column_name 45 | SpannerDataType string // data_type 46 | IsNotNull bool // not_null 47 | IsPrimaryKey bool // is_primary_key 48 | IsGenerated bool // is_generated 49 | IsHidden bool // is_hidden 50 | } 51 | 52 | // Index is a template item for a index into a table. 53 | type Index struct { 54 | Name string // Go like (CamelCase) index name 55 | FuncName string // `By` + Name 56 | LegacyFuncName string // `By` + Type name + Field names 57 | Type *Type 58 | Fields []*Field 59 | StoringFields []*Field 60 | NullableFields []*Field 61 | IndexName string // index name 62 | IsUnique bool // the index is unique ro not 63 | IsPrimary bool // the index is primary key or not 64 | } 65 | -------------------------------------------------------------------------------- /internal/loader_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "go.mercari.io/yo/models" 8 | ) 9 | 10 | func Test_setIndexesToTables(t *testing.T) { 11 | tests := []struct { 12 | table map[string]*Type 13 | ix map[string]*Index 14 | result map[string]int 15 | }{ 16 | { 17 | table: map[string]*Type{ 18 | "TableA": &Type{ 19 | Indexes: []*Index{}, 20 | }, 21 | }, 22 | ix: map[string]*Index{ 23 | "TableA_Index1": &Index{ 24 | Type: &Type{ 25 | Table: &models.Table{ 26 | TableName: "TableA", 27 | }, 28 | }, 29 | }, 30 | "TableA_Index2": &Index{ 31 | Type: &Type{ 32 | Table: &models.Table{ 33 | TableName: "TableA", 34 | }, 35 | }, 36 | }, 37 | }, 38 | result: map[string]int{ 39 | "TableA": 2, 40 | }, 41 | }, 42 | { 43 | table: map[string]*Type{ 44 | "TableA": &Type{ 45 | Indexes: []*Index{}, 46 | }, 47 | "TableB": &Type{ 48 | Indexes: []*Index{}, 49 | }, 50 | }, 51 | ix: map[string]*Index{ 52 | "TableA_Index1": &Index{ 53 | Type: &Type{ 54 | Table: &models.Table{ 55 | TableName: "TableA", 56 | }, 57 | }, 58 | }, 59 | "TableA_Index2": &Index{ 60 | Type: &Type{ 61 | Table: &models.Table{ 62 | TableName: "TableA", 63 | }, 64 | }, 65 | }, 66 | }, 67 | result: map[string]int{ 68 | "TableA": 2, 69 | "TableB": 0, 70 | }, 71 | }, 72 | { 73 | table: map[string]*Type{ 74 | "TableA": &Type{ 75 | Indexes: []*Index{}, 76 | }, 77 | "TableB": &Type{ 78 | Indexes: []*Index{}, 79 | }, 80 | }, 81 | ix: map[string]*Index{ 82 | "TableA_Index1": &Index{ 83 | Type: &Type{ 84 | Table: &models.Table{ 85 | TableName: "TableA", 86 | }, 87 | }, 88 | }, 89 | "TableA_Index2": &Index{ 90 | Type: &Type{ 91 | Table: &models.Table{ 92 | TableName: "TableA", 93 | }, 94 | }, 95 | }, 96 | "TableB_Index1": &Index{ 97 | Type: &Type{ 98 | Table: &models.Table{ 99 | TableName: "TableB", 100 | }, 101 | }, 102 | }, 103 | "TableB_Index2forTableA_Hoge": &Index{ 104 | Type: &Type{ 105 | Table: &models.Table{ 106 | TableName: "TableB", 107 | }, 108 | }, 109 | }, 110 | }, 111 | result: map[string]int{ 112 | "TableA": 2, 113 | "TableB": 2, 114 | }, 115 | }, 116 | } 117 | 118 | for i, tt := range tests { 119 | t.Run(fmt.Sprintf("case:%d", i), func(t *testing.T) { 120 | setIndexesToTables(tt.table, tt.ix) 121 | for k, v := range tt.table { 122 | if len(v.Indexes) != tt.result[k] { 123 | t.Errorf("error. want:%d got:%d", tt.result[k], len(v.Indexes)) 124 | } 125 | } 126 | }) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /internal/inflector.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package internal 21 | 22 | import ( 23 | "io/ioutil" 24 | 25 | "github.com/gedex/inflector" 26 | "github.com/jinzhu/inflection" 27 | "gopkg.in/yaml.v2" 28 | ) 29 | 30 | type Inflector interface { 31 | Singularize(string) string 32 | Pluralize(string) string 33 | } 34 | 35 | type DefaultInflector struct{} 36 | type RuleInflector struct{} 37 | 38 | func (i *DefaultInflector) Singularize(s string) string { 39 | return inflector.Singularize(s) 40 | } 41 | func (i *DefaultInflector) Pluralize(s string) string { 42 | return inflector.Pluralize(s) 43 | } 44 | 45 | func (i *RuleInflector) Singularize(s string) string { 46 | return inflection.Singular(s) 47 | } 48 | func (i *RuleInflector) Pluralize(s string) string { 49 | return inflection.Plural(s) 50 | } 51 | 52 | func NewInflector(ruleFile string) (Inflector, error) { 53 | if ruleFile == "" { 54 | return &DefaultInflector{}, nil 55 | } 56 | err := registerRule(ruleFile) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return &RuleInflector{}, nil 61 | } 62 | 63 | type InflectRule struct { 64 | Singuler string `yaml:"singular"` 65 | Plural string `yaml:"plural"` 66 | } 67 | 68 | func registerRule(inflectionRuleFile string) error { 69 | rules, err := readRule(inflectionRuleFile) 70 | if err != nil { 71 | return err 72 | } 73 | if rules != nil { 74 | for _, irr := range rules { 75 | inflection.AddIrregular(irr.Singuler, irr.Plural) 76 | } 77 | } 78 | return nil 79 | } 80 | 81 | func readRule(ruleFile string) ([]InflectRule, error) { 82 | data, err := ioutil.ReadFile(ruleFile) 83 | if err != nil { 84 | return nil, err 85 | } 86 | var rules []InflectRule 87 | err = yaml.Unmarshal(data, &rules) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return rules, nil 92 | 93 | } 94 | -------------------------------------------------------------------------------- /v2/Makefile: -------------------------------------------------------------------------------- 1 | export SPANNER_EMULATOR_HOST ?= localhost:9010 2 | export SPANNER_EMULATOR_HOST_REST ?= localhost:9020 3 | export SPANNER_PROJECT_NAME ?= yo-test 4 | export SPANNER_INSTANCE_NAME ?= yo-test 5 | export SPANNER_DATABASE_NAME ?= yo-test 6 | 7 | YOBIN ?= yo 8 | 9 | .PHONY: help 10 | help: ## show this help message. 11 | @grep -hE '^\S+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 12 | 13 | all: build 14 | 15 | build: ## build yo command and regenerate template bin 16 | go build 17 | 18 | deps: 19 | go mod download 20 | 21 | .PHONY: test 22 | test: ## run test 23 | @echo run tests with spanner emulator 24 | go test -race -v ./... 25 | 26 | recreate-templates:: ## recreate templates 27 | rm -rf module/builtin/templates && mkdir module/builtin/templates 28 | $(YOBIN) create-template --template-path module/builtin/templates 29 | 30 | USE_DDL ?= false 31 | ifeq ($(USE_DDL),true) 32 | GENERATE_OPT = ./test/testdata/schema.sql --from-ddl 33 | else 34 | GENERATE_OPT = $(SPANNER_PROJECT_NAME) $(SPANNER_INSTANCE_NAME) $(SPANNER_DATABASE_NAME) 35 | endif 36 | 37 | testdata: ## generate test models 38 | $(MAKE) -j4 testdata/default testdata/legacy_default testdata/dump_types 39 | 40 | testdata-from-ddl: ## generate test models 41 | $(MAKE) USE_DDL=true testdata 42 | 43 | testdata/default: 44 | rm -rf test/testmodels/default && mkdir -p test/testmodels/default 45 | $(YOBIN) generate $(GENERATE_OPT) --config test/testdata/config.yml --package models --out test/testmodels/default/ 46 | 47 | testdata/legacy_default: 48 | rm -rf test/testmodels/legacy_default && mkdir -p test/testmodels/legacy_default 49 | $(YOBIN) generate $(GENERATE_OPT) --config test/testdata/config.yml --use-legacy-index-module --package models --out test/testmodels/legacy_default/ 50 | 51 | testdata/dump_types: 52 | rm -rf test/testmodels/dump_types && mkdir -p test/testmodels/dump_types 53 | $(YOBIN) generate $(GENERATE_OPT) --suffix '.txt' --disable-format --disable-default-modules --type-module test/testdata/dump_types.go.tpl --package models --out test/testmodels/dump_types/ 54 | 55 | .PHONY: check-diff 56 | 57 | EXPECTED_FILES := \ 58 | v2/test/testmodels/default/composite_primary_key.yo.go \ 59 | v2/test/testmodels/default/custom_composite_primary_key.yo.go \ 60 | v2/test/testmodels/legacy_default/composite_primary_key.yo.go \ 61 | v2/test/testmodels/legacy_default/custom_composite_primary_key.yo.go 62 | 63 | check-diff: 64 | @echo "Checking git diff against expected files..." 65 | @ACTUAL_FILES=$$(git diff --name-only | grep -v '^go\.mod$$' | grep -v '^go\.sum$$' | sort) ; \ 66 | SORTED_EXPECTED_FILES=$$(echo "$(EXPECTED_FILES)" | tr ' ' '\n' | sort) ; \ 67 | if [ "$$ACTUAL_FILES" = "$$SORTED_EXPECTED_FILES" ]; then \ 68 | echo "Success: git diff output matches the expected file list." ; \ 69 | else \ 70 | echo "Error: git diff output does not match the expected file list." ; \ 71 | echo "--- Expected Files ---" ; \ 72 | echo "$$SORTED_EXPECTED_FILES" ; \ 73 | echo "--- Actual Files ---" ; \ 74 | echo "$$ACTUAL_FILES" ; \ 75 | exit 1 ; \ 76 | fi -------------------------------------------------------------------------------- /v2/go.mod: -------------------------------------------------------------------------------- 1 | module go.mercari.io/yo/v2 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | cloud.google.com/go v0.118.3 7 | cloud.google.com/go/spanner v1.76.1 8 | github.com/cloudspannerecosystem/memefish v0.5.0 9 | github.com/google/go-cmp v0.6.0 10 | github.com/googleapis/gax-go/v2 v2.14.1 11 | github.com/jinzhu/inflection v1.0.0 12 | github.com/kenshaw/snaker v0.2.0 13 | github.com/spf13/cobra v1.7.0 14 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d 15 | google.golang.org/api v0.222.0 16 | google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 17 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 18 | google.golang.org/grpc v1.70.0 19 | google.golang.org/protobuf v1.36.5 20 | gopkg.in/yaml.v2 v2.4.0 21 | ) 22 | 23 | require ( 24 | cel.dev/expr v0.19.0 // indirect 25 | cloud.google.com/go/auth v0.14.1 // indirect 26 | cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect 27 | cloud.google.com/go/compute/metadata v0.6.0 // indirect 28 | cloud.google.com/go/iam v1.4.0 // indirect 29 | cloud.google.com/go/longrunning v0.6.4 // indirect 30 | cloud.google.com/go/monitoring v1.24.0 // indirect 31 | github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect 32 | github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect 33 | github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect 34 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 35 | github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect 36 | github.com/envoyproxy/go-control-plane v0.13.1 // indirect 37 | github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect 38 | github.com/felixge/httpsnoop v1.0.4 // indirect 39 | github.com/go-logr/logr v1.4.2 // indirect 40 | github.com/go-logr/stdr v1.2.2 // indirect 41 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 42 | github.com/google/s2a-go v0.1.9 // indirect 43 | github.com/google/uuid v1.6.0 // indirect 44 | github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect 45 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 46 | github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect 47 | github.com/spf13/pflag v1.0.5 // indirect 48 | go.opencensus.io v0.24.0 // indirect 49 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 50 | go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect 51 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect 52 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect 53 | go.opentelemetry.io/otel v1.34.0 // indirect 54 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 55 | go.opentelemetry.io/otel/sdk v1.34.0 // indirect 56 | go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect 57 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 58 | golang.org/x/crypto v0.36.0 // indirect 59 | golang.org/x/mod v0.17.0 // indirect 60 | golang.org/x/net v0.38.0 // indirect 61 | golang.org/x/oauth2 v0.26.0 // indirect 62 | golang.org/x/sync v0.12.0 // indirect 63 | golang.org/x/sys v0.31.0 // indirect 64 | golang.org/x/text v0.23.0 // indirect 65 | golang.org/x/time v0.10.0 // indirect 66 | google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2 // indirect 67 | ) 68 | -------------------------------------------------------------------------------- /internal/argtype.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package internal 21 | 22 | // ArgType is the type that specifies the command line arguments. 23 | type ArgType struct { 24 | // Project is the GCP project string 25 | Project string 26 | 27 | // Instance is the instance string 28 | Instance string 29 | 30 | // Database is the database string 31 | Database string 32 | 33 | // CustomTypesFile is the path for custom table field type definition file (xx.yml) 34 | CustomTypesFile string 35 | 36 | // Out is the output path. If Out is a file, then that will be used as the 37 | // path. If Out is a directory, then the output file will be 38 | // Out/<$CWD>.yo.go 39 | Out string 40 | 41 | // Suffix is the output suffix for filenames. 42 | Suffix string 43 | 44 | // SingleFile when toggled changes behavior so that output is to one f ile. 45 | SingleFile bool 46 | 47 | // Package is the name used to generate package headers. If not specified, 48 | // the name of the output directory will be used instead. 49 | Package string 50 | 51 | // CustomTypePackage is the Go package name to use for unknown types. 52 | CustomTypePackage string 53 | 54 | // TargetTables allows the user to specify table names which should be 55 | // handled by yo in the generated code. 56 | TargetTables []string 57 | 58 | // IgnoreFields allows the user to specify field names which should not be 59 | // handled by yo in the generated code. 60 | IgnoreFields []string 61 | 62 | // IgnoreTables allows the user to specify table names which should not be 63 | // handled by yo in the generated code. 64 | IgnoreTables []string 65 | 66 | // TemplatePath is the path to use the user supplied templates instead of 67 | // the built in versions. 68 | TemplatePath string 69 | 70 | // Tags is the list of build tags to add to generated Go files. 71 | Tags string 72 | 73 | Path string 74 | Filename string 75 | FilenameUnderscore bool 76 | 77 | // DDLFilepath is the filepath of the ddl file. 78 | DDLFilepath string 79 | 80 | // FromDDL indicates generating from ddl file or not. 81 | FromDDL bool 82 | 83 | // InflectionRuleFile is custom inflection rule file. 84 | InflectionRuleFile string 85 | } 86 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Google Open Source Community Guidelines 2 | 3 | At Google, we recognize and celebrate the creativity and collaboration of open 4 | source contributors and the diversity of skills, experiences, cultures, and 5 | opinions they bring to the projects and communities they participate in. 6 | 7 | Every one of Google's open source projects and communities are inclusive 8 | environments, based on treating all individuals respectfully, regardless of 9 | gender identity and expression, sexual orientation, disabilities, 10 | neurodiversity, physical appearance, body size, ethnicity, nationality, race, 11 | age, religion, or similar personal characteristic. 12 | 13 | We value diverse opinions, but we value respectful behavior more. 14 | 15 | Respectful behavior includes: 16 | 17 | * Being considerate, kind, constructive, and helpful. 18 | * Not engaging in demeaning, discriminatory, harassing, hateful, sexualized, or 19 | physically threatening behavior, speech, and imagery. 20 | * Not engaging in unwanted physical contact. 21 | 22 | Some Google open source projects [may adopt][] an explicit project code of 23 | conduct, which may have additional detailed expectations for participants. Most 24 | of those projects will use our [modified Contributor Covenant][]. 25 | 26 | [may adopt]: https://opensource.google/docs/releasing/preparing/#conduct 27 | [modified Contributor Covenant]: https://opensource.google/docs/releasing/template/CODE_OF_CONDUCT/ 28 | 29 | ## Resolve peacefully 30 | 31 | We do not believe that all conflict is necessarily bad; healthy debate and 32 | disagreement often yields positive results. However, it is never okay to be 33 | disrespectful. 34 | 35 | If you see someone behaving disrespectfully, you are encouraged to address the 36 | behavior directly with those involved. Many issues can be resolved quickly and 37 | easily, and this gives people more control over the outcome of their dispute. 38 | If you are unable to resolve the matter for any reason, or if the behavior is 39 | threatening or harassing, report it. We are dedicated to providing an 40 | environment where participants feel welcome and safe. 41 | 42 | ## Reporting problems 43 | 44 | Some Google open source projects may adopt a project-specific code of conduct. 45 | In those cases, a Google employee will be identified as the Project Steward, 46 | who will receive and handle reports of code of conduct violations. In the event 47 | that a project hasn’t identified a Project Steward, you can report problems by 48 | emailing opensource@google.com. 49 | 50 | We will investigate every complaint, but you may not receive a direct response. 51 | We will use our discretion in determining when and how to follow up on reported 52 | incidents, which may range from not taking action to permanent expulsion from 53 | the project and project-sponsored spaces. We will notify the accused of the 54 | report and provide them an opportunity to discuss it before any action is 55 | taken. The identity of the reporter will be omitted from the details of the 56 | report supplied to the accused. In potentially harmful situations, such as 57 | ongoing harassment or threats to anyone's safety, we may take action without 58 | notice. 59 | 60 | *This document was adapted from the [IndieWeb Code of Conduct][] and can also 61 | be found at .* 62 | 63 | [IndieWeb Code of Conduct]: https://indieweb.org/code-of-conduct 64 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module go.mercari.io/yo 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.5 6 | 7 | require ( 8 | cloud.google.com/go v0.118.3 9 | cloud.google.com/go/spanner v1.76.1 10 | github.com/cloudspannerecosystem/memefish v0.5.0 11 | github.com/gedex/inflector v0.0.0-20170307190818-16278e9db813 12 | github.com/google/go-cmp v0.6.0 13 | github.com/googleapis/gax-go/v2 v2.14.1 14 | github.com/jessevdk/go-assets v0.0.0-20160921144138-4f4301a06e15 15 | github.com/jinzhu/inflection v1.0.0 16 | github.com/kenshaw/snaker v0.2.0 17 | github.com/spf13/cobra v1.6.1 18 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d 19 | google.golang.org/api v0.222.0 20 | google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 21 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 22 | google.golang.org/grpc v1.70.0 23 | google.golang.org/protobuf v1.36.5 24 | gopkg.in/yaml.v2 v2.4.0 25 | ) 26 | 27 | require ( 28 | cel.dev/expr v0.19.0 // indirect 29 | cloud.google.com/go/auth v0.14.1 // indirect 30 | cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect 31 | cloud.google.com/go/compute/metadata v0.6.0 // indirect 32 | cloud.google.com/go/iam v1.4.0 // indirect 33 | cloud.google.com/go/longrunning v0.6.4 // indirect 34 | cloud.google.com/go/monitoring v1.24.0 // indirect 35 | github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect 36 | github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect 37 | github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect 38 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 39 | github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect 40 | github.com/envoyproxy/go-control-plane v0.13.1 // indirect 41 | github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect 42 | github.com/felixge/httpsnoop v1.0.4 // indirect 43 | github.com/go-logr/logr v1.4.2 // indirect 44 | github.com/go-logr/stdr v1.2.2 // indirect 45 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 46 | github.com/google/s2a-go v0.1.9 // indirect 47 | github.com/google/uuid v1.6.0 // indirect 48 | github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect 49 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 50 | github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect 51 | github.com/spf13/pflag v1.0.5 // indirect 52 | go.opencensus.io v0.24.0 // indirect 53 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 54 | go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect 55 | go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect 56 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect 57 | go.opentelemetry.io/otel v1.34.0 // indirect 58 | go.opentelemetry.io/otel/metric v1.34.0 // indirect 59 | go.opentelemetry.io/otel/sdk v1.34.0 // indirect 60 | go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect 61 | go.opentelemetry.io/otel/trace v1.34.0 // indirect 62 | golang.org/x/crypto v0.36.0 // indirect 63 | golang.org/x/mod v0.17.0 // indirect 64 | golang.org/x/net v0.38.0 // indirect 65 | golang.org/x/oauth2 v0.26.0 // indirect 66 | golang.org/x/sync v0.12.0 // indirect 67 | golang.org/x/sys v0.31.0 // indirect 68 | golang.org/x/text v0.23.0 // indirect 69 | golang.org/x/time v0.10.0 // indirect 70 | google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2 // indirect 71 | ) 72 | -------------------------------------------------------------------------------- /v2/test/testmodels/default/out_of_order_primary_key.yo.go: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | 3 | // Package models contains the types. 4 | package models 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "cloud.google.com/go/spanner" 11 | ) 12 | 13 | // OutOfOrderPrimaryKey represents a row from 'OutOfOrderPrimaryKeys'. 14 | type OutOfOrderPrimaryKey struct { 15 | PKey1 string `spanner:"PKey1" json:"PKey1"` // PKey1 16 | PKey2 string `spanner:"PKey2" json:"PKey2"` // PKey2 17 | PKey3 string `spanner:"PKey3" json:"PKey3"` // PKey3 18 | } 19 | 20 | func OutOfOrderPrimaryKeyPrimaryKeys() []string { 21 | return []string{ 22 | "PKey2", 23 | "PKey1", 24 | "PKey3", 25 | } 26 | } 27 | 28 | func OutOfOrderPrimaryKeyColumns() []string { 29 | return []string{ 30 | "PKey1", 31 | "PKey2", 32 | "PKey3", 33 | } 34 | } 35 | 36 | func OutOfOrderPrimaryKeyWritableColumns() []string { 37 | return []string{ 38 | "PKey1", 39 | "PKey2", 40 | "PKey3", 41 | } 42 | } 43 | 44 | func (ooopk *OutOfOrderPrimaryKey) columnsToPtrs(cols []string) ([]interface{}, error) { 45 | ret := make([]interface{}, 0, len(cols)) 46 | for _, col := range cols { 47 | switch col { 48 | case "PKey1": 49 | ret = append(ret, yoDecode(&ooopk.PKey1)) 50 | case "PKey2": 51 | ret = append(ret, yoDecode(&ooopk.PKey2)) 52 | case "PKey3": 53 | ret = append(ret, yoDecode(&ooopk.PKey3)) 54 | default: 55 | return nil, fmt.Errorf("unknown column: %s", col) 56 | } 57 | } 58 | return ret, nil 59 | } 60 | 61 | func (ooopk *OutOfOrderPrimaryKey) columnsToValues(cols []string) ([]interface{}, error) { 62 | ret := make([]interface{}, 0, len(cols)) 63 | for _, col := range cols { 64 | switch col { 65 | case "PKey1": 66 | ret = append(ret, yoEncode(ooopk.PKey1)) 67 | case "PKey2": 68 | ret = append(ret, yoEncode(ooopk.PKey2)) 69 | case "PKey3": 70 | ret = append(ret, yoEncode(ooopk.PKey3)) 71 | default: 72 | return nil, fmt.Errorf("unknown column: %s", col) 73 | } 74 | } 75 | 76 | return ret, nil 77 | } 78 | 79 | // newOutOfOrderPrimaryKey_Decoder returns a decoder which reads a row from *spanner.Row 80 | // into OutOfOrderPrimaryKey. The decoder is not goroutine-safe. Don't use it concurrently. 81 | func newOutOfOrderPrimaryKey_Decoder(cols []string) func(*spanner.Row) (*OutOfOrderPrimaryKey, error) { 82 | return func(row *spanner.Row) (*OutOfOrderPrimaryKey, error) { 83 | var ooopk OutOfOrderPrimaryKey 84 | ptrs, err := ooopk.columnsToPtrs(cols) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | if err := row.Columns(ptrs...); err != nil { 90 | return nil, err 91 | } 92 | 93 | return &ooopk, nil 94 | } 95 | } 96 | 97 | // Insert returns a Mutation to insert a row into a table. If the row already 98 | // exists, the write or transaction fails. 99 | func (ooopk *OutOfOrderPrimaryKey) Insert(ctx context.Context) *spanner.Mutation { 100 | values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyWritableColumns()) 101 | return spanner.Insert("OutOfOrderPrimaryKeys", OutOfOrderPrimaryKeyWritableColumns(), values) 102 | } 103 | 104 | // Delete deletes the OutOfOrderPrimaryKey from the database. 105 | func (ooopk *OutOfOrderPrimaryKey) Delete(ctx context.Context) *spanner.Mutation { 106 | values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyPrimaryKeys()) 107 | return spanner.Delete("OutOfOrderPrimaryKeys", spanner.Key(values)) 108 | } 109 | -------------------------------------------------------------------------------- /v2/test/testmodels/legacy_default/out_of_order_primary_key.yo.go: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | 3 | // Package models contains the types. 4 | package models 5 | 6 | import ( 7 | "context" 8 | "fmt" 9 | 10 | "cloud.google.com/go/spanner" 11 | ) 12 | 13 | // OutOfOrderPrimaryKey represents a row from 'OutOfOrderPrimaryKeys'. 14 | type OutOfOrderPrimaryKey struct { 15 | PKey1 string `spanner:"PKey1" json:"PKey1"` // PKey1 16 | PKey2 string `spanner:"PKey2" json:"PKey2"` // PKey2 17 | PKey3 string `spanner:"PKey3" json:"PKey3"` // PKey3 18 | } 19 | 20 | func OutOfOrderPrimaryKeyPrimaryKeys() []string { 21 | return []string{ 22 | "PKey2", 23 | "PKey1", 24 | "PKey3", 25 | } 26 | } 27 | 28 | func OutOfOrderPrimaryKeyColumns() []string { 29 | return []string{ 30 | "PKey1", 31 | "PKey2", 32 | "PKey3", 33 | } 34 | } 35 | 36 | func OutOfOrderPrimaryKeyWritableColumns() []string { 37 | return []string{ 38 | "PKey1", 39 | "PKey2", 40 | "PKey3", 41 | } 42 | } 43 | 44 | func (ooopk *OutOfOrderPrimaryKey) columnsToPtrs(cols []string) ([]interface{}, error) { 45 | ret := make([]interface{}, 0, len(cols)) 46 | for _, col := range cols { 47 | switch col { 48 | case "PKey1": 49 | ret = append(ret, yoDecode(&ooopk.PKey1)) 50 | case "PKey2": 51 | ret = append(ret, yoDecode(&ooopk.PKey2)) 52 | case "PKey3": 53 | ret = append(ret, yoDecode(&ooopk.PKey3)) 54 | default: 55 | return nil, fmt.Errorf("unknown column: %s", col) 56 | } 57 | } 58 | return ret, nil 59 | } 60 | 61 | func (ooopk *OutOfOrderPrimaryKey) columnsToValues(cols []string) ([]interface{}, error) { 62 | ret := make([]interface{}, 0, len(cols)) 63 | for _, col := range cols { 64 | switch col { 65 | case "PKey1": 66 | ret = append(ret, yoEncode(ooopk.PKey1)) 67 | case "PKey2": 68 | ret = append(ret, yoEncode(ooopk.PKey2)) 69 | case "PKey3": 70 | ret = append(ret, yoEncode(ooopk.PKey3)) 71 | default: 72 | return nil, fmt.Errorf("unknown column: %s", col) 73 | } 74 | } 75 | 76 | return ret, nil 77 | } 78 | 79 | // newOutOfOrderPrimaryKey_Decoder returns a decoder which reads a row from *spanner.Row 80 | // into OutOfOrderPrimaryKey. The decoder is not goroutine-safe. Don't use it concurrently. 81 | func newOutOfOrderPrimaryKey_Decoder(cols []string) func(*spanner.Row) (*OutOfOrderPrimaryKey, error) { 82 | return func(row *spanner.Row) (*OutOfOrderPrimaryKey, error) { 83 | var ooopk OutOfOrderPrimaryKey 84 | ptrs, err := ooopk.columnsToPtrs(cols) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | if err := row.Columns(ptrs...); err != nil { 90 | return nil, err 91 | } 92 | 93 | return &ooopk, nil 94 | } 95 | } 96 | 97 | // Insert returns a Mutation to insert a row into a table. If the row already 98 | // exists, the write or transaction fails. 99 | func (ooopk *OutOfOrderPrimaryKey) Insert(ctx context.Context) *spanner.Mutation { 100 | values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyWritableColumns()) 101 | return spanner.Insert("OutOfOrderPrimaryKeys", OutOfOrderPrimaryKeyWritableColumns(), values) 102 | } 103 | 104 | // Delete deletes the OutOfOrderPrimaryKey from the database. 105 | func (ooopk *OutOfOrderPrimaryKey) Delete(ctx context.Context) *spanner.Mutation { 106 | values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyPrimaryKeys()) 107 | return spanner.Delete("OutOfOrderPrimaryKeys", spanner.Key(values)) 108 | } 109 | -------------------------------------------------------------------------------- /test/testmodels/default/outoforderprimarykey.yo.go: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | // Package models contains the types. 3 | package models 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "cloud.google.com/go/spanner" 10 | ) 11 | 12 | // OutOfOrderPrimaryKey represents a row from 'OutOfOrderPrimaryKeys'. 13 | type OutOfOrderPrimaryKey struct { 14 | PKey1 string `spanner:"PKey1" json:"PKey1"` // PKey1 15 | PKey2 string `spanner:"PKey2" json:"PKey2"` // PKey2 16 | PKey3 string `spanner:"PKey3" json:"PKey3"` // PKey3 17 | } 18 | 19 | func OutOfOrderPrimaryKeyPrimaryKeys() []string { 20 | return []string{ 21 | "PKey2", 22 | "PKey1", 23 | "PKey3", 24 | } 25 | } 26 | 27 | func OutOfOrderPrimaryKeyColumns() []string { 28 | return []string{ 29 | "PKey1", 30 | "PKey2", 31 | "PKey3", 32 | } 33 | } 34 | 35 | func OutOfOrderPrimaryKeyWritableColumns() []string { 36 | return []string{ 37 | "PKey1", 38 | "PKey2", 39 | "PKey3", 40 | } 41 | } 42 | 43 | func (ooopk *OutOfOrderPrimaryKey) columnsToPtrs(cols []string, customPtrs map[string]interface{}) ([]interface{}, error) { 44 | ret := make([]interface{}, 0, len(cols)) 45 | for _, col := range cols { 46 | if val, ok := customPtrs[col]; ok { 47 | ret = append(ret, val) 48 | continue 49 | } 50 | 51 | switch col { 52 | case "PKey1": 53 | ret = append(ret, &ooopk.PKey1) 54 | case "PKey2": 55 | ret = append(ret, &ooopk.PKey2) 56 | case "PKey3": 57 | ret = append(ret, &ooopk.PKey3) 58 | default: 59 | return nil, fmt.Errorf("unknown column: %s", col) 60 | } 61 | } 62 | return ret, nil 63 | } 64 | 65 | func (ooopk *OutOfOrderPrimaryKey) columnsToValues(cols []string) ([]interface{}, error) { 66 | ret := make([]interface{}, 0, len(cols)) 67 | for _, col := range cols { 68 | switch col { 69 | case "PKey1": 70 | ret = append(ret, ooopk.PKey1) 71 | case "PKey2": 72 | ret = append(ret, ooopk.PKey2) 73 | case "PKey3": 74 | ret = append(ret, ooopk.PKey3) 75 | default: 76 | return nil, fmt.Errorf("unknown column: %s", col) 77 | } 78 | } 79 | 80 | return ret, nil 81 | } 82 | 83 | // newOutOfOrderPrimaryKey_Decoder returns a decoder which reads a row from *spanner.Row 84 | // into OutOfOrderPrimaryKey. The decoder is not goroutine-safe. Don't use it concurrently. 85 | func newOutOfOrderPrimaryKey_Decoder(cols []string) func(*spanner.Row) (*OutOfOrderPrimaryKey, error) { 86 | customPtrs := map[string]interface{}{} 87 | 88 | return func(row *spanner.Row) (*OutOfOrderPrimaryKey, error) { 89 | var ooopk OutOfOrderPrimaryKey 90 | ptrs, err := ooopk.columnsToPtrs(cols, customPtrs) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | if err := row.Columns(ptrs...); err != nil { 96 | return nil, err 97 | } 98 | 99 | return &ooopk, nil 100 | } 101 | } 102 | 103 | // Insert returns a Mutation to insert a row into a table. If the row already 104 | // exists, the write or transaction fails. 105 | func (ooopk *OutOfOrderPrimaryKey) Insert(ctx context.Context) *spanner.Mutation { 106 | values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyWritableColumns()) 107 | return spanner.Insert("OutOfOrderPrimaryKeys", OutOfOrderPrimaryKeyWritableColumns(), values) 108 | } 109 | 110 | // Delete deletes the OutOfOrderPrimaryKey from the database. 111 | func (ooopk *OutOfOrderPrimaryKey) Delete(ctx context.Context) *spanner.Mutation { 112 | values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyPrimaryKeys()) 113 | return spanner.Delete("OutOfOrderPrimaryKeys", spanner.Key(values)) 114 | } 115 | -------------------------------------------------------------------------------- /test/testmodels/underscore/out_of_order_primary_key.yo.go: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | // Package models contains the types. 3 | package models 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "cloud.google.com/go/spanner" 10 | ) 11 | 12 | // OutOfOrderPrimaryKey represents a row from 'OutOfOrderPrimaryKeys'. 13 | type OutOfOrderPrimaryKey struct { 14 | PKey1 string `spanner:"PKey1" json:"PKey1"` // PKey1 15 | PKey2 string `spanner:"PKey2" json:"PKey2"` // PKey2 16 | PKey3 string `spanner:"PKey3" json:"PKey3"` // PKey3 17 | } 18 | 19 | func OutOfOrderPrimaryKeyPrimaryKeys() []string { 20 | return []string{ 21 | "PKey2", 22 | "PKey1", 23 | "PKey3", 24 | } 25 | } 26 | 27 | func OutOfOrderPrimaryKeyColumns() []string { 28 | return []string{ 29 | "PKey1", 30 | "PKey2", 31 | "PKey3", 32 | } 33 | } 34 | 35 | func OutOfOrderPrimaryKeyWritableColumns() []string { 36 | return []string{ 37 | "PKey1", 38 | "PKey2", 39 | "PKey3", 40 | } 41 | } 42 | 43 | func (ooopk *OutOfOrderPrimaryKey) columnsToPtrs(cols []string, customPtrs map[string]interface{}) ([]interface{}, error) { 44 | ret := make([]interface{}, 0, len(cols)) 45 | for _, col := range cols { 46 | if val, ok := customPtrs[col]; ok { 47 | ret = append(ret, val) 48 | continue 49 | } 50 | 51 | switch col { 52 | case "PKey1": 53 | ret = append(ret, &ooopk.PKey1) 54 | case "PKey2": 55 | ret = append(ret, &ooopk.PKey2) 56 | case "PKey3": 57 | ret = append(ret, &ooopk.PKey3) 58 | default: 59 | return nil, fmt.Errorf("unknown column: %s", col) 60 | } 61 | } 62 | return ret, nil 63 | } 64 | 65 | func (ooopk *OutOfOrderPrimaryKey) columnsToValues(cols []string) ([]interface{}, error) { 66 | ret := make([]interface{}, 0, len(cols)) 67 | for _, col := range cols { 68 | switch col { 69 | case "PKey1": 70 | ret = append(ret, ooopk.PKey1) 71 | case "PKey2": 72 | ret = append(ret, ooopk.PKey2) 73 | case "PKey3": 74 | ret = append(ret, ooopk.PKey3) 75 | default: 76 | return nil, fmt.Errorf("unknown column: %s", col) 77 | } 78 | } 79 | 80 | return ret, nil 81 | } 82 | 83 | // newOutOfOrderPrimaryKey_Decoder returns a decoder which reads a row from *spanner.Row 84 | // into OutOfOrderPrimaryKey. The decoder is not goroutine-safe. Don't use it concurrently. 85 | func newOutOfOrderPrimaryKey_Decoder(cols []string) func(*spanner.Row) (*OutOfOrderPrimaryKey, error) { 86 | customPtrs := map[string]interface{}{} 87 | 88 | return func(row *spanner.Row) (*OutOfOrderPrimaryKey, error) { 89 | var ooopk OutOfOrderPrimaryKey 90 | ptrs, err := ooopk.columnsToPtrs(cols, customPtrs) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | if err := row.Columns(ptrs...); err != nil { 96 | return nil, err 97 | } 98 | 99 | return &ooopk, nil 100 | } 101 | } 102 | 103 | // Insert returns a Mutation to insert a row into a table. If the row already 104 | // exists, the write or transaction fails. 105 | func (ooopk *OutOfOrderPrimaryKey) Insert(ctx context.Context) *spanner.Mutation { 106 | values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyWritableColumns()) 107 | return spanner.Insert("OutOfOrderPrimaryKeys", OutOfOrderPrimaryKeyWritableColumns(), values) 108 | } 109 | 110 | // Delete deletes the OutOfOrderPrimaryKey from the database. 111 | func (ooopk *OutOfOrderPrimaryKey) Delete(ctx context.Context) *spanner.Mutation { 112 | values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyPrimaryKeys()) 113 | return spanner.Delete("OutOfOrderPrimaryKeys", spanner.Key(values)) 114 | } 115 | -------------------------------------------------------------------------------- /test/testmodels/customtypes/outoforderprimarykey.yo.go: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | // Package customtypes contains the types. 3 | package customtypes 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | 9 | "cloud.google.com/go/spanner" 10 | ) 11 | 12 | // OutOfOrderPrimaryKey represents a row from 'OutOfOrderPrimaryKeys'. 13 | type OutOfOrderPrimaryKey struct { 14 | PKey1 string `spanner:"PKey1" json:"PKey1"` // PKey1 15 | PKey2 string `spanner:"PKey2" json:"PKey2"` // PKey2 16 | PKey3 string `spanner:"PKey3" json:"PKey3"` // PKey3 17 | } 18 | 19 | func OutOfOrderPrimaryKeyPrimaryKeys() []string { 20 | return []string{ 21 | "PKey2", 22 | "PKey1", 23 | "PKey3", 24 | } 25 | } 26 | 27 | func OutOfOrderPrimaryKeyColumns() []string { 28 | return []string{ 29 | "PKey1", 30 | "PKey2", 31 | "PKey3", 32 | } 33 | } 34 | 35 | func OutOfOrderPrimaryKeyWritableColumns() []string { 36 | return []string{ 37 | "PKey1", 38 | "PKey2", 39 | "PKey3", 40 | } 41 | } 42 | 43 | func (ooopk *OutOfOrderPrimaryKey) columnsToPtrs(cols []string, customPtrs map[string]interface{}) ([]interface{}, error) { 44 | ret := make([]interface{}, 0, len(cols)) 45 | for _, col := range cols { 46 | if val, ok := customPtrs[col]; ok { 47 | ret = append(ret, val) 48 | continue 49 | } 50 | 51 | switch col { 52 | case "PKey1": 53 | ret = append(ret, &ooopk.PKey1) 54 | case "PKey2": 55 | ret = append(ret, &ooopk.PKey2) 56 | case "PKey3": 57 | ret = append(ret, &ooopk.PKey3) 58 | default: 59 | return nil, fmt.Errorf("unknown column: %s", col) 60 | } 61 | } 62 | return ret, nil 63 | } 64 | 65 | func (ooopk *OutOfOrderPrimaryKey) columnsToValues(cols []string) ([]interface{}, error) { 66 | ret := make([]interface{}, 0, len(cols)) 67 | for _, col := range cols { 68 | switch col { 69 | case "PKey1": 70 | ret = append(ret, ooopk.PKey1) 71 | case "PKey2": 72 | ret = append(ret, ooopk.PKey2) 73 | case "PKey3": 74 | ret = append(ret, ooopk.PKey3) 75 | default: 76 | return nil, fmt.Errorf("unknown column: %s", col) 77 | } 78 | } 79 | 80 | return ret, nil 81 | } 82 | 83 | // newOutOfOrderPrimaryKey_Decoder returns a decoder which reads a row from *spanner.Row 84 | // into OutOfOrderPrimaryKey. The decoder is not goroutine-safe. Don't use it concurrently. 85 | func newOutOfOrderPrimaryKey_Decoder(cols []string) func(*spanner.Row) (*OutOfOrderPrimaryKey, error) { 86 | customPtrs := map[string]interface{}{} 87 | 88 | return func(row *spanner.Row) (*OutOfOrderPrimaryKey, error) { 89 | var ooopk OutOfOrderPrimaryKey 90 | ptrs, err := ooopk.columnsToPtrs(cols, customPtrs) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | if err := row.Columns(ptrs...); err != nil { 96 | return nil, err 97 | } 98 | 99 | return &ooopk, nil 100 | } 101 | } 102 | 103 | // Insert returns a Mutation to insert a row into a table. If the row already 104 | // exists, the write or transaction fails. 105 | func (ooopk *OutOfOrderPrimaryKey) Insert(ctx context.Context) *spanner.Mutation { 106 | values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyWritableColumns()) 107 | return spanner.Insert("OutOfOrderPrimaryKeys", OutOfOrderPrimaryKeyWritableColumns(), values) 108 | } 109 | 110 | // Delete deletes the OutOfOrderPrimaryKey from the database. 111 | func (ooopk *OutOfOrderPrimaryKey) Delete(ctx context.Context) *spanner.Mutation { 112 | values, _ := ooopk.columnsToValues(OutOfOrderPrimaryKeyPrimaryKeys()) 113 | return spanner.Delete("OutOfOrderPrimaryKeys", spanner.Key(values)) 114 | } 115 | -------------------------------------------------------------------------------- /v2/generator/templates.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package generator 21 | 22 | import ( 23 | "fmt" 24 | "io" 25 | "text/template" 26 | 27 | "go.mercari.io/yo/v2/models" 28 | "go.mercari.io/yo/v2/module" 29 | ) 30 | 31 | var ( 32 | // KnownTypeMap is the collection of known Go types. 33 | KnownTypeMap = map[string]bool{ 34 | "bool": true, 35 | "string": true, 36 | "byte": true, 37 | "rune": true, 38 | "int": true, 39 | "int8": true, 40 | "int16": true, 41 | "int32": true, 42 | "int64": true, 43 | "uint": true, 44 | "uint8": true, 45 | "uint16": true, 46 | "uint32": true, 47 | "uint64": true, 48 | "float32": true, 49 | "float64": true, 50 | "Slice": true, 51 | "StringSlice": true, 52 | } 53 | 54 | ShortNameTypeMap = map[string]string{ 55 | "bool": "b", 56 | "string": "s", 57 | "byte": "b", 58 | "rune": "r", 59 | "int": "i", 60 | "int8": "i", 61 | "int16": "i", 62 | "int32": "i", 63 | "int64": "i", 64 | "uint": "u", 65 | "uint8": "u", 66 | "uint16": "u", 67 | "uint32": "u", 68 | "uint64": "u", 69 | "float32": "f", 70 | "float64": "f", 71 | } 72 | 73 | ConflictedShortNames = map[string]bool{ 74 | "context": true, 75 | "errors": true, 76 | "fmt": true, 77 | "regexp": true, 78 | "strings": true, 79 | "time": true, 80 | "iterator": true, 81 | "spanner": true, 82 | "civil": true, 83 | "codes": true, 84 | "status": true, 85 | } 86 | ) 87 | 88 | // basicDataSet is used for template data for yo_db and yo_package. 89 | type basicDataSet struct { 90 | BuildTag string 91 | Package string 92 | Schema *models.Schema 93 | } 94 | 95 | // templateSet is a set of templates. 96 | type templateSet struct { 97 | funcs template.FuncMap 98 | } 99 | 100 | // Execute executes a specified template in the template set using the supplied 101 | // obj as its parameters and writing the output to w. 102 | func (ts *templateSet) Execute(w io.Writer, mod module.Module, obj interface{}) error { 103 | buf, err := mod.Load() 104 | if err != nil { 105 | return fmt.Errorf("Load module(%s): %v", mod.Name(), err) 106 | } 107 | 108 | // parse template 109 | tpl, err := template.New(mod.Name()).Funcs(ts.funcs).Parse(string(buf)) 110 | if err != nil { 111 | return fmt.Errorf("Parse module(%s): %v", mod.Name(), err) 112 | } 113 | 114 | if err := tpl.Execute(w, obj); err != nil { 115 | return fmt.Errorf("Execute module(%s): %v", mod.Name(), err) 116 | } 117 | 118 | return nil 119 | } 120 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | test: 5 | docker: 6 | - image: golang:1.24-bullseye 7 | environment: 8 | SPANNER_EMULATOR_HOST: localhost:9010 9 | SPANNER_EMULATOR_HOST_REST: localhost:9020 10 | PROJECT: yo-test 11 | INSTANCE: yo-test 12 | DATABASE: yo-test 13 | - image: gcr.io/cloud-spanner-emulator/emulator:1.5.36 14 | working_directory: /go/src/github.com/cloudspannerecosystem/yo 15 | steps: 16 | - checkout 17 | - run: 18 | name: install deps 19 | command: | 20 | make deps 21 | 22 | - run: 23 | name: build 24 | command: | 25 | make build 26 | 27 | - run: 28 | name: regenerate templates and check diff 29 | command: | 30 | make regen 31 | git diff --quiet tplbin/ 32 | 33 | - run: 34 | name: regenerate testdata-from-ddl and check diff 35 | command: | 36 | make testdata-from-ddl YOBIN=./yo 37 | git diff --quiet test/ 38 | 39 | - run: 40 | name: regenerate template files and check diff 41 | command: | 42 | make recreate-templates YOBIN=./yo 43 | git diff --quiet templates/ 44 | 45 | - run: 46 | name: run integration test 47 | command: | 48 | make test 49 | 50 | - run: 51 | name: regenerate testdata and check diff 52 | command: | 53 | make testdata YOBIN=./yo 54 | make check-diff 55 | 56 | v2test: 57 | docker: 58 | - image: golang:1.24-bullseye 59 | environment: 60 | SPANNER_EMULATOR_HOST: localhost:9010 61 | SPANNER_EMULATOR_HOST_REST: localhost:9020 62 | PROJECT: yo-test 63 | INSTANCE: yo-test 64 | DATABASE: yo-test 65 | - image: gcr.io/cloud-spanner-emulator/emulator:1.5.34 66 | working_directory: /go/src/github.com/cloudspannerecosystem/yo 67 | steps: 68 | - checkout 69 | - run: 70 | name: install deps 71 | command: | 72 | make -C v2 deps 73 | 74 | - run: 75 | name: build 76 | command: | 77 | make -C v2 build 78 | 79 | - run: 80 | name: regenerate template files and check diff 81 | command: | 82 | make -C v2 recreate-templates YOBIN=./yo 83 | git diff --quiet v2/module/builtin/templates/ 84 | 85 | - run: 86 | name: run integration test 87 | command: | 88 | make -C v2 test 89 | 90 | - run: 91 | name: regenerate testdata-from-ddl and check diff 92 | command: | 93 | make -C v2 testdata-from-ddl YOBIN=./yo 94 | git diff --quiet test/ 95 | 96 | - run: 97 | name: regenerate testdata and check diff 98 | command: | 99 | make -C v2 testdata YOBIN=./yo 100 | make -C v2 check-diff 101 | check_lint: 102 | docker: 103 | - image: golang:1.24-bullseye 104 | working_directory: /go/src/github.com/cloudspannerecosystem/yo 105 | steps: 106 | - checkout 107 | - run: 108 | name: check lint 109 | command: | 110 | make check_lint 111 | check_go_mod: 112 | docker: 113 | - image: golang:1.24-bullseye 114 | working_directory: /go/src/github.com/cloudspannerecosystem/yo 115 | steps: 116 | - checkout 117 | - run: 118 | name: check go mod 119 | command: | 120 | make check_gomod 121 | 122 | workflows: 123 | version: 2 124 | build-workflow: 125 | jobs: 126 | - test 127 | - v2test 128 | - check_lint 129 | - check_go_mod 130 | -------------------------------------------------------------------------------- /v2/generator/generator_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package generator 21 | 22 | import ( 23 | "os" 24 | "path/filepath" 25 | "testing" 26 | 27 | "github.com/google/go-cmp/cmp" 28 | "go.mercari.io/yo/v2/internal" 29 | "go.mercari.io/yo/v2/models" 30 | ) 31 | 32 | type fakeLoader struct{} 33 | 34 | func (*fakeLoader) NthParam(int) string { 35 | return "@" 36 | } 37 | 38 | func newTestGenerator(t *testing.T) *Generator { 39 | t.Helper() 40 | 41 | inflector, err := internal.NewInflector(nil) 42 | if err != nil { 43 | t.Fatalf("failed to create inflector: %v", err) 44 | } 45 | 46 | return NewGenerator(&fakeLoader{}, inflector, GeneratorOption{ 47 | PackageName: "yotest", 48 | Tags: "", 49 | FilenameSuffix: ".yo.go", 50 | BaseDir: t.TempDir(), 51 | }) 52 | } 53 | 54 | func TestGenerator(t *testing.T) { 55 | table := []struct { 56 | name string 57 | schema *models.Schema 58 | expectedFilesDir string 59 | compareBaseFile bool 60 | }{ 61 | { 62 | name: "BaseOnly", 63 | schema: &models.Schema{}, 64 | expectedFilesDir: "testdata/empty", 65 | compareBaseFile: true, 66 | }, 67 | } 68 | 69 | for _, tc := range table { 70 | t.Run(tc.name, func(t *testing.T) { 71 | g := newTestGenerator(t) 72 | if err := g.Generate(tc.schema); err != nil { 73 | t.Fatalf("failed to generate: %v", err) 74 | } 75 | 76 | if err := filepath.Walk(g.baseDir, func(path string, info os.FileInfo, err error) error { 77 | if info.IsDir() { 78 | return nil 79 | } 80 | 81 | if !tc.compareBaseFile && info.Name() == "yo_db.yo.go" { 82 | return nil 83 | } 84 | 85 | t.Logf("generated file path: %v\n", path) 86 | 87 | actualContent, err := os.ReadFile(path) 88 | if err != nil { 89 | t.Fatalf("failed to read file: %v", err) 90 | } 91 | 92 | expectedFilePath := filepath.Join(tc.expectedFilesDir, info.Name()) 93 | expectedContent, err := os.ReadFile(expectedFilePath) 94 | if os.IsNotExist(err) { 95 | err = os.MkdirAll(filepath.Join(tc.expectedFilesDir), 0766) 96 | if err != nil { 97 | t.Fatal(err) 98 | } 99 | err = os.WriteFile(expectedFilePath, actualContent, 0444) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | return nil 104 | } else if err != nil { 105 | t.Fatal(err) 106 | } 107 | 108 | if diff := cmp.Diff(actualContent, expectedContent); diff != "" { 109 | t.Errorf("%s (-got, +want)\n%s", expectedFilePath, diff) 110 | } 111 | 112 | return nil 113 | }); err != nil { 114 | t.Fatalf("filepath.Walk failed: %v", err) 115 | } 116 | }) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /v2/internal/inflector_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package internal 21 | 22 | import ( 23 | "testing" 24 | 25 | "github.com/jinzhu/inflection" 26 | ) 27 | 28 | type inflectionPattern struct { 29 | single string 30 | plural string 31 | } 32 | 33 | var inflectionPatterns = []inflectionPattern{ 34 | {"alias", "aliases"}, 35 | {"belief", "beliefs"}, 36 | {"bureau", "bureaus"}, 37 | {"bus", "buses"}, 38 | {"cafe", "cafes"}, 39 | {"chef", "chefs"}, 40 | {"chief", "chiefs"}, 41 | {"cookie", "cookies"}, 42 | {"crisis", "crises"}, 43 | {"drive", "drives"}, 44 | {"foe", "foes"}, 45 | {"foot", "feet"}, 46 | {"glove", "gloves"}, 47 | {"goose", "geese"}, 48 | {"halo", "halos"}, 49 | {"hero", "heroes"}, 50 | {"hive", "hives"}, 51 | {"house", "houses"}, 52 | {"index", "indices"}, 53 | {"matrix", "matrices"}, 54 | {"media", "media"}, 55 | {"menu", "menus"}, 56 | {"multimedia", "multimedia"}, 57 | {"news", "news"}, 58 | {"objective", "objectives"}, 59 | {"octopus", "octopuses"}, 60 | {"person", "people"}, 61 | {"photo", "photos"}, 62 | {"piano", "pianos"}, 63 | {"potato", "potatoes"}, 64 | {"powerhouse", "powerhouses"}, 65 | {"quiz", "quizzes"}, 66 | {"roof", "roofs"}, 67 | {"roof", "roofs"}, 68 | {"shoe", "shoes"}, 69 | {"tax", "taxes"}, 70 | {"thief", "thieves"}, 71 | {"tooth", "teeth"}, 72 | {"vertex", "vertices"}, 73 | {"virus", "viri"}, 74 | {"wave", "waves"}, 75 | {"wife", "wives"}, 76 | {"wolf", "wolves"}, 77 | {`atlas`, `atlases`}, 78 | {`beef`, `beefs`}, 79 | {`brother`, `brothers`}, 80 | {`cafe`, `cafes`}, 81 | {`child`, `children`}, 82 | {`cookie`, `cookies`}, 83 | {`corpus`, `corpuses`}, 84 | {`cow`, `cows`}, 85 | {`drive`, `drives`}, 86 | {`ganglion`, `ganglions`}, 87 | {`genie`, `genies`}, 88 | {`genus`, `genera`}, 89 | {`graffito`, `graffiti`}, 90 | {`harddrive`, `harddrives`}, 91 | {`hero`, `heroes`}, 92 | {`hoof`, `hoofs`}, 93 | {`loaf`, `loaves`}, 94 | {`man`, `men`}, 95 | {`money`, `money`}, 96 | {`mongoose`, `mongooses`}, 97 | {`move`, `moves`}, 98 | {`mythos`, `mythoi`}, 99 | {`niche`, `niches`}, 100 | {`numen`, `numina`}, 101 | {`occiput`, `occiputs`}, 102 | {`octopus`, `octopuses`}, 103 | {`opus`, `opuses`}, 104 | {`ox`, `oxen`}, 105 | {`potato`, `potatoes`}, 106 | {`soliloquy`, `soliloquies`}, 107 | {`testis`, `testes`}, 108 | {`trilby`, `trilbys`}, 109 | {`turf`, `turfs`}, 110 | } 111 | 112 | func init() { 113 | registerRule(nil) 114 | } 115 | 116 | func TestInflection(t *testing.T) { 117 | for _, tc := range inflectionPatterns { 118 | s := inflection.Plural(tc.single) 119 | if s != tc.plural { 120 | t.Errorf("Pluralize(%s): got %q, expected: %q", tc.single, s, tc.plural) 121 | } 122 | s = inflection.Singular(tc.plural) 123 | if s != tc.single { 124 | t.Errorf("Singular(%s): got %q, expected: %q", tc.plural, s, tc.single) 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /v2/loader/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package loader 21 | 22 | import ( 23 | "regexp" 24 | "strconv" 25 | "strings" 26 | 27 | "go.mercari.io/yo/v2/internal" 28 | ) 29 | 30 | var lengthRegexp = regexp.MustCompile(`\(([0-9]+|MAX)\)$`) 31 | 32 | // SpanParseType parse a Spanner type into a Go type based on the column 33 | // definition. 34 | func parseSpannerType(dt string, nullable bool) (int, string, string) { 35 | nilVal := "nil" 36 | length := -1 37 | 38 | // separate type and length from dt with length such as STRING(32) or BYTES(256) 39 | m := lengthRegexp.FindStringSubmatchIndex(dt) 40 | if m != nil { 41 | lengthStr := dt[m[2]:m[3]] 42 | if lengthStr == "MAX" { 43 | length = -1 44 | } else { 45 | l, err := strconv.Atoi(lengthStr) 46 | if err != nil { 47 | panic("could not convert precision") 48 | } 49 | length = l 50 | } 51 | 52 | // trim length from dt 53 | dt = dt[:m[0]] + dt[m[1]:] 54 | } 55 | 56 | var typ string 57 | switch dt { 58 | case "BOOL": 59 | nilVal = "false" 60 | typ = "bool" 61 | if nullable { 62 | nilVal = "spanner.NullBool{}" 63 | typ = "spanner.NullBool" 64 | } 65 | 66 | case "STRING": 67 | nilVal = `""` 68 | typ = "string" 69 | if nullable { 70 | nilVal = "spanner.NullString{}" 71 | typ = "spanner.NullString" 72 | } 73 | 74 | case "INT64": 75 | nilVal = "0" 76 | typ = "int64" 77 | if nullable { 78 | nilVal = "spanner.NullInt64{}" 79 | typ = "spanner.NullInt64" 80 | } 81 | 82 | case "FLOAT64": 83 | nilVal = "0.0" 84 | typ = "float64" 85 | if nullable { 86 | nilVal = "spanner.NullFloat64{}" 87 | typ = "spanner.NullFloat64" 88 | } 89 | 90 | case "BYTES": 91 | typ = "[]byte" 92 | 93 | case "TIMESTAMP": 94 | nilVal = "time.Time{}" 95 | typ = "time.Time" 96 | if nullable { 97 | nilVal = "spanner.NullTime{}" 98 | typ = "spanner.NullTime" 99 | } 100 | 101 | case "DATE": 102 | nilVal = "civil.Date{}" 103 | typ = "civil.Date" 104 | if nullable { 105 | nilVal = "spanner.NullDate{}" 106 | typ = "spanner.NullDate" 107 | } 108 | 109 | case "NUMERIC": 110 | nilVal = "big.Rat{}" 111 | typ = "big.Rat" 112 | if nullable { 113 | nilVal = "spanner.NullNumeric{}" 114 | typ = "spanner.NullNumeric" 115 | } 116 | 117 | case "JSON": 118 | nilVal = `spanner.NullJSON{Valid: true}` 119 | typ = "spanner.NullJSON" 120 | if nullable { 121 | nilVal = `spanner.NullJSON{}` 122 | } 123 | 124 | default: 125 | if strings.HasPrefix(dt, "ARRAY<") { 126 | eleDataType := strings.TrimSuffix(strings.TrimPrefix(dt, "ARRAY<"), ">") 127 | _, _, eleTyp := parseSpannerType(eleDataType, false) 128 | typ, nilVal = "[]"+eleTyp, "nil" 129 | if !nullable { 130 | nilVal = typ + "{}" 131 | } 132 | break 133 | } 134 | 135 | typ = internal.SnakeToCamel(dt) 136 | nilVal = typ + "{}" 137 | } 138 | 139 | return length, nilVal, typ 140 | } 141 | -------------------------------------------------------------------------------- /v2/module/builtin/templates/operation.go.tpl: -------------------------------------------------------------------------------- 1 | {{- $short := (shortName .Name "err" "res" "sqlstr" "db" "YOLog") -}} 2 | {{- $table := (.TableName) -}} 3 | 4 | // Insert returns a Mutation to insert a row into a table. If the row already 5 | // exists, the write or transaction fails. 6 | func ({{ $short }} *{{ .Name }}) Insert(ctx context.Context) *spanner.Mutation { 7 | values, _ := {{ $short }}.columnsToValues({{ .Name }}WritableColumns()) 8 | return spanner.Insert("{{ $table }}", {{ .Name }}WritableColumns(), values) 9 | } 10 | 11 | {{ if ne (len .Fields) (len .PrimaryKeyFields) }} 12 | // Update returns a Mutation to update a row in a table. If the row does not 13 | // already exist, the write or transaction fails. 14 | func ({{ $short }} *{{ .Name }}) Update(ctx context.Context) *spanner.Mutation { 15 | values, _ := {{ $short }}.columnsToValues({{ .Name }}WritableColumns()) 16 | return spanner.Update("{{ $table }}", {{ .Name }}WritableColumns(), values) 17 | } 18 | 19 | // InsertOrUpdate returns a Mutation to insert a row into a table. If the row 20 | // already exists, it updates it instead. Any column values not explicitly 21 | // written are preserved. 22 | func ({{ $short }} *{{ .Name }}) InsertOrUpdate(ctx context.Context) *spanner.Mutation { 23 | values, _ := {{ $short }}.columnsToValues({{ .Name }}WritableColumns()) 24 | return spanner.InsertOrUpdate("{{ $table }}", {{ .Name }}WritableColumns(), values) 25 | } 26 | 27 | // Replace returns a Mutation to insert a row into a table, deleting any 28 | // existing row. Unlike InsertOrUpdate, this means any values not explicitly 29 | // written become NULL. 30 | func ({{ $short }} *{{ .Name }}) Replace(ctx context.Context) *spanner.Mutation { 31 | values, _ := {{ $short }}.columnsToValues({{ .Name }}WritableColumns()) 32 | return spanner.Replace("{{ $table }}", {{ .Name }}WritableColumns(), values) 33 | } 34 | 35 | // UpdateColumns returns a Mutation to update specified columns of a row in a table. 36 | func ({{ $short }} *{{ .Name }}) UpdateColumns(ctx context.Context, cols ...string) (*spanner.Mutation, error) { 37 | // add primary keys to columns to update by primary keys 38 | colsWithPKeys := append(cols, {{ .Name }}PrimaryKeys()...) 39 | 40 | values, err := {{ $short }}.columnsToValues(colsWithPKeys) 41 | if err != nil { 42 | return nil, newErrorWithCode(codes.InvalidArgument, "{{ .Name }}.UpdateColumns", "{{ $table }}", err) 43 | } 44 | 45 | return spanner.Update("{{ $table }}", colsWithPKeys, values), nil 46 | } 47 | 48 | // Find{{ .Name }} gets a {{ .Name }} by primary key 49 | func Find{{ .Name }}(ctx context.Context, db YODB{{ goParams .PrimaryKeyFields true true }}) (*{{ .Name }}, error) { 50 | _key := spanner.Key{ {{ goEncodedParams .PrimaryKeyFields false }} } 51 | row, err := db.ReadRow(ctx, "{{ $table }}", _key, {{ .Name }}Columns()) 52 | if err != nil { 53 | return nil, newError("Find{{ .Name }}", "{{ $table }}", err) 54 | } 55 | 56 | decoder := new{{ .Name }}_Decoder({{ .Name}}Columns()) 57 | {{ $short }}, err := decoder(row) 58 | if err != nil { 59 | return nil, newErrorWithCode(codes.Internal, "Find{{ .Name }}", "{{ $table }}", err) 60 | } 61 | 62 | return {{ $short }}, nil 63 | } 64 | 65 | // Read{{ .Name }} retrieves multiples rows from {{ .Name }} by KeySet as a slice. 66 | func Read{{ .Name }}(ctx context.Context, db YODB, keys spanner.KeySet) ([]*{{ .Name }}, error) { 67 | var res []*{{ .Name }} 68 | 69 | decoder := new{{ .Name }}_Decoder({{ .Name}}Columns()) 70 | 71 | rows := db.Read(ctx, "{{ $table }}", keys, {{ .Name }}Columns()) 72 | err := rows.Do(func(row *spanner.Row) error { 73 | {{ $short }}, err := decoder(row) 74 | if err != nil { 75 | return err 76 | } 77 | res = append(res, {{ $short }}) 78 | 79 | return nil 80 | }) 81 | if err != nil { 82 | return nil, newErrorWithCode(codes.Internal, "Read{{ .Name }}", "{{ $table }}", err) 83 | } 84 | 85 | return res, nil 86 | } 87 | {{ end }} 88 | 89 | // Delete deletes the {{ .Name }} from the database. 90 | func ({{ $short }} *{{ .Name }}) Delete(ctx context.Context) *spanner.Mutation { 91 | values, _ := {{ $short }}.columnsToValues({{ .Name }}PrimaryKeys()) 92 | return spanner.Delete("{{ $table }}", spanner.Key(values)) 93 | } 94 | -------------------------------------------------------------------------------- /generator/templates.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package generator 21 | 22 | import ( 23 | "io" 24 | "text/template" 25 | 26 | "go.mercari.io/yo/internal" 27 | ) 28 | 29 | // TemplateType represents a template type. 30 | type TemplateType uint 31 | 32 | // the order here will be the alter the output order per file. 33 | const ( 34 | TypeTemplate TemplateType = iota 35 | IndexTemplate 36 | 37 | // always last 38 | YOTemplate 39 | ) 40 | 41 | // String returns the name for the associated template type. 42 | func (tt TemplateType) String() string { 43 | var s string 44 | switch tt { 45 | case YOTemplate: 46 | s = "yo_db" 47 | case TypeTemplate: 48 | s = "type" 49 | case IndexTemplate: 50 | s = "index" 51 | default: 52 | panic("unknown TemplateType") 53 | } 54 | return s 55 | } 56 | 57 | var ( 58 | // KnownTypeMap is the collection of known Go types. 59 | KnownTypeMap = map[string]bool{ 60 | "bool": true, 61 | "string": true, 62 | "byte": true, 63 | "rune": true, 64 | "int": true, 65 | "int8": true, 66 | "int16": true, 67 | "int32": true, 68 | "int64": true, 69 | "uint": true, 70 | "uint8": true, 71 | "uint16": true, 72 | "uint32": true, 73 | "uint64": true, 74 | "float32": true, 75 | "float64": true, 76 | "Slice": true, 77 | "StringSlice": true, 78 | } 79 | 80 | ShortNameTypeMap = map[string]string{ 81 | "bool": "b", 82 | "string": "s", 83 | "byte": "b", 84 | "rune": "r", 85 | "int": "i", 86 | "int8": "i", 87 | "int16": "i", 88 | "int32": "i", 89 | "int64": "i", 90 | "uint": "u", 91 | "uint8": "u", 92 | "uint16": "u", 93 | "uint32": "u", 94 | "uint64": "u", 95 | "float32": "f", 96 | "float64": "f", 97 | } 98 | 99 | ConflictedShortNames = map[string]bool{ 100 | "context": true, 101 | "errors": true, 102 | "fmt": true, 103 | "regexp": true, 104 | "strings": true, 105 | "time": true, 106 | "iterator": true, 107 | "spanner": true, 108 | "civil": true, 109 | "codes": true, 110 | "status": true, 111 | } 112 | ) 113 | 114 | // basicDataSet is used for template data for yo_db and yo_package. 115 | type basicDataSet struct { 116 | Package string 117 | TableMap map[string]*internal.Type 118 | } 119 | 120 | // templateSet is a set of templates. 121 | type templateSet struct { 122 | funcs template.FuncMap 123 | l func(string) ([]byte, error) 124 | tpls map[string]*template.Template 125 | } 126 | 127 | // Execute executes a specified template in the template set using the supplied 128 | // obj as its parameters and writing the output to w. 129 | func (ts *templateSet) Execute(w io.Writer, name string, obj interface{}) error { 130 | tpl, ok := ts.tpls[name] 131 | if !ok { 132 | // attempt to load and parse the template 133 | buf, err := ts.l(name) 134 | if err != nil { 135 | return err 136 | } 137 | 138 | // parse template 139 | tpl, err = template.New(name).Funcs(ts.funcs).Parse(string(buf)) 140 | if err != nil { 141 | return err 142 | } 143 | } 144 | 145 | return tpl.Execute(w, obj) 146 | } 147 | -------------------------------------------------------------------------------- /v2/module/builtin/templates/yo_db.go.tpl: -------------------------------------------------------------------------------- 1 | // YODB is the common interface for database operations. 2 | type YODB interface { 3 | ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error) 4 | Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator 5 | ReadUsingIndex(ctx context.Context, table, index string, keys spanner.KeySet, columns []string) (ri *spanner.RowIterator) 6 | Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator 7 | } 8 | 9 | // YOLog provides the log func used by generated queries. 10 | var YOLog = func(context.Context, string, ...interface{}) { } 11 | 12 | func newError(method, table string, err error) error { 13 | code := spanner.ErrCode(err) 14 | return newErrorWithCode(code, method, table, err) 15 | } 16 | 17 | func newErrorWithCode(code codes.Code, method, table string, err error) error { 18 | return &yoError{ 19 | method: method, 20 | table: table, 21 | err: err, 22 | code: code, 23 | } 24 | } 25 | 26 | type yoError struct { 27 | err error 28 | method string 29 | table string 30 | code codes.Code 31 | } 32 | 33 | func (e yoError) Error() string { 34 | return fmt.Sprintf("yo error in %s(%s): %v", e.method, e.table, e.err) 35 | } 36 | 37 | func (e yoError) Unwrap() error { 38 | return e.err 39 | } 40 | 41 | func (e yoError) DBTableName() string { 42 | return e.table 43 | } 44 | 45 | // GRPCStatus implements a conversion to a gRPC status using `status.Convert(error)`. 46 | // If the error is originated from the Spanner library, this returns a gRPC status of 47 | // the original error. It may contain details of the status such as RetryInfo. 48 | func (e yoError) GRPCStatus() *status.Status { 49 | var ae *apierror.APIError 50 | if errors.As(e.err, &ae) { 51 | return status.Convert(ae) 52 | } 53 | 54 | return status.New(e.code, e.Error()) 55 | } 56 | 57 | func (e yoError) Timeout() bool { return e.code == codes.DeadlineExceeded } 58 | func (e yoError) Temporary() bool { return e.code == codes.DeadlineExceeded } 59 | func (e yoError) NotFound() bool { return e.code == codes.NotFound } 60 | 61 | // yoEncode encodes primitive types that spanner library does not support into spanner types before 62 | // passing to spanner functions. Suppotted primitive types and user defined types that implement 63 | // spanner.Encoder interface are handled in encoding phase inside spanner libirary. 64 | func yoEncode(v interface{}) interface{} { 65 | switch vv := v.(type) { 66 | case int8: 67 | return int64(vv) 68 | case uint8: 69 | return int64(vv) 70 | case int16: 71 | return int64(vv) 72 | case uint16: 73 | return int64(vv) 74 | case int32: 75 | return int64(vv) 76 | case uint32: 77 | return int64(vv) 78 | case uint64: 79 | return int64(vv) 80 | default: 81 | return v 82 | } 83 | } 84 | 85 | // yoDecode wraps primitive types that spanner library does not support to decode from spanner types 86 | // by yoPrimitiveDecoder before passing to spanner functions. Supported primitive types and 87 | // user defined types that implement spanner.Decoder interface are handled in decoding phase inside 88 | // spanner libirary. 89 | func yoDecode(ptr interface{}) interface{} { 90 | switch ptr.(type) { 91 | case *int8, *uint8, *int16, *uint16, *int32, *uint32, *uint64: 92 | return &yoPrimitiveDecoder{val: ptr} 93 | default: 94 | return ptr 95 | } 96 | } 97 | 98 | type yoPrimitiveDecoder struct { 99 | val interface{} 100 | } 101 | 102 | func (y *yoPrimitiveDecoder) DecodeSpanner(val interface{}) error { 103 | strVal, ok := val.(string) 104 | if !ok { 105 | return spanner.ToSpannerError(status.Errorf(codes.FailedPrecondition, "failed to decode customField: %T(%v)", val, val)) 106 | } 107 | 108 | intVal, err := strconv.ParseInt(strVal, 10, 64) 109 | if err != nil { 110 | return spanner.ToSpannerError(status.Errorf(codes.FailedPrecondition, "%v wasn't correctly encoded: <%v>", val, err)) 111 | } 112 | 113 | switch vv := y.val.(type) { 114 | case *int8: 115 | *vv = int8(intVal) 116 | case *uint8: 117 | *vv = uint8(intVal) 118 | case *int16: 119 | *vv = int16(intVal) 120 | case *uint16: 121 | *vv = uint16(intVal) 122 | case *int32: 123 | *vv = int32(intVal) 124 | case *uint32: 125 | *vv = uint32(intVal) 126 | case *uint64: 127 | *vv = uint64(intVal) 128 | default: 129 | return status.Errorf(codes.Internal, "unexpected type for yoPrimitiveDecoder: %T", y.val) 130 | } 131 | 132 | return nil 133 | } 134 | -------------------------------------------------------------------------------- /v2/generator/buffer.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package generator 21 | 22 | import ( 23 | "bytes" 24 | "fmt" 25 | "os" 26 | "sort" 27 | "strings" 28 | 29 | "golang.org/x/tools/imports" 30 | ) 31 | 32 | // importsOptions is the same as x/tools/cmd/goimports options except Fragment. 33 | var importsOptions = &imports.Options{ 34 | TabWidth: 8, 35 | TabIndent: true, 36 | Comments: true, 37 | } 38 | 39 | type FileBuffer struct { 40 | FileName string 41 | BaseName string 42 | 43 | Header []byte 44 | Chunks []*TBuf 45 | 46 | TempDir string 47 | TempFilePath string 48 | } 49 | 50 | func (f *FileBuffer) WriteTempFile() error { 51 | file, err := os.CreateTemp(f.TempDir, fmt.Sprintf("%s_*", f.BaseName)) 52 | if err != nil { 53 | return fmt.Errorf("failed to create temp file for %s: %v", f.BaseName, err) 54 | } 55 | 56 | if err := f.writeChunks(file); err != nil { 57 | _ = file.Close() 58 | return fmt.Errorf("failed to write temp file for %s: %v", f.BaseName, err) 59 | } 60 | 61 | if err := file.Close(); err != nil { 62 | return fmt.Errorf("failed to close file for %s: %v", f.BaseName, err) 63 | } 64 | 65 | f.TempFilePath = file.Name() 66 | return nil 67 | } 68 | 69 | func (f *FileBuffer) writeChunks(file *os.File) error { 70 | // write a header to the file 71 | if _, err := file.Write(f.Header); err != nil { 72 | return err 73 | } 74 | 75 | chunks := TBufSlice(f.Chunks) 76 | 77 | // sort chunks 78 | sort.Sort(chunks) 79 | 80 | // write chunks to the file in order 81 | for i, chunk := range chunks { 82 | // add new line between chunks 83 | if i != 0 { 84 | _, _ = file.Write([]byte("\n")) 85 | } 86 | 87 | // check if generated template is only whitespace/empty 88 | bufStr := strings.TrimSpace(chunk.Buf.String()) 89 | if len(bufStr) == 0 { 90 | continue 91 | } 92 | 93 | if _, err := chunk.Buf.WriteTo(file); err != nil { 94 | return err 95 | } 96 | } 97 | 98 | return nil 99 | } 100 | 101 | func (f *FileBuffer) Postprocess(disableFormat bool) error { 102 | if !disableFormat { 103 | // run gofmt for the temp file 104 | formatted, err := imports.Process(f.TempFilePath, nil, importsOptions) 105 | if err != nil { 106 | return fmt.Errorf("failed to fmt file for %s: %v", f.BaseName, err) 107 | } 108 | 109 | // overwrite the tempfile by gofmt result 110 | // since abs file exists, set perm to 0 111 | if err := os.WriteFile(f.TempFilePath, formatted, 0); err != nil { 112 | return fmt.Errorf("failed to formatted file for %s: %v", f.BaseName, err) 113 | } 114 | } 115 | 116 | // change permission 117 | if err := os.Chmod(f.TempFilePath, 0666); err != nil { 118 | return fmt.Errorf("failed to change file permission for %s: %v", f.BaseName, err) 119 | } 120 | 121 | return nil 122 | } 123 | 124 | func (f *FileBuffer) Finalize() error { 125 | if err := os.Rename(f.TempFilePath, f.FileName); err != nil { 126 | return fmt.Errorf("failed to put file for %s: %v", f.BaseName, err) 127 | } 128 | 129 | return nil 130 | } 131 | 132 | // TBuf is to hold the executed templates. 133 | type TBuf struct { 134 | Name string 135 | Buf *bytes.Buffer 136 | } 137 | 138 | // TBufSlice is a slice of TBuf compatible with sort.Interface. 139 | type TBufSlice []*TBuf 140 | 141 | func (t TBufSlice) Len() int { 142 | return len(t) 143 | } 144 | 145 | func (t TBufSlice) Swap(i, j int) { 146 | t[i], t[j] = t[j], t[i] 147 | } 148 | 149 | func (t TBufSlice) Less(i, j int) bool { 150 | return strings.Compare(t[i].Name, t[j].Name) < 0 151 | } 152 | -------------------------------------------------------------------------------- /cmd/generate.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package cmd 21 | 22 | import ( 23 | "fmt" 24 | 25 | "github.com/spf13/cobra" 26 | "go.mercari.io/yo/generator" 27 | "go.mercari.io/yo/internal" 28 | "go.mercari.io/yo/loaders" 29 | ) 30 | 31 | var ( 32 | generateOpts = internal.ArgType{} 33 | generateCmd = &cobra.Command{ 34 | Use: "generate", 35 | Short: "yo generate generates Go code from ddl file.", 36 | Args: func(cmd *cobra.Command, args []string) error { 37 | if l := len(args); l != 1 && l != 3 { 38 | return fmt.Errorf("must specify 1 or 3 arguments") 39 | } 40 | return nil 41 | }, 42 | Example: ` # Generate models from ddl under models directory 43 | yo generate schema.sql --from-ddl -o models 44 | 45 | # Generate models from ddl under models directory with custom types 46 | yo generate schema.sql --from-ddl -o models --custom-types-file custom_column_types.yml 47 | 48 | # Generate models under models directory 49 | yo generate $SPANNER_PROJECT_NAME $SPANNER_INSTANCE_NAME $SPANNER_DATABASE_NAME -o models 50 | 51 | # Generate models under models directory with custom types 52 | yo generate $SPANNER_PROJECT_NAME $SPANNER_INSTANCE_NAME $SPANNER_DATABASE_NAME -o models --custom-types-file custom_column_types.yml 53 | `, 54 | RunE: func(cmd *cobra.Command, args []string) error { 55 | if err := processArgs(&generateOpts, args); err != nil { 56 | return err 57 | } 58 | 59 | inflector, err := internal.NewInflector(generateOpts.InflectionRuleFile) 60 | if err != nil { 61 | return fmt.Errorf("load inflection rule failed: %v", err) 62 | } 63 | var loader *internal.TypeLoader 64 | if generateOpts.FromDDL { 65 | spannerLoader, err := loaders.NewSpannerLoaderFromDDL(args[0]) 66 | if err != nil { 67 | return fmt.Errorf("error: %v", err) 68 | } 69 | loader = internal.NewTypeLoader(spannerLoader, inflector) 70 | } else { 71 | spannerClient, err := connectSpanner(&rootOpts) 72 | if err != nil { 73 | return fmt.Errorf("error: %v", err) 74 | } 75 | spannerLoader := loaders.NewSpannerLoader(spannerClient) 76 | loader = internal.NewTypeLoader(spannerLoader, inflector) 77 | } 78 | 79 | // load custom type definitions 80 | if generateOpts.CustomTypesFile != "" { 81 | if err := loader.LoadCustomTypes(generateOpts.CustomTypesFile); err != nil { 82 | return fmt.Errorf("load custom types file failed: %v", err) 83 | } 84 | } 85 | 86 | // load defs into type map 87 | tableMap, ixMap, err := loader.LoadSchema(&generateOpts) 88 | if err != nil { 89 | return fmt.Errorf("error: %v", err) 90 | } 91 | 92 | g := generator.NewGenerator(loader, inflector, generator.GeneratorOption{ 93 | PackageName: generateOpts.Package, 94 | Tags: generateOpts.Tags, 95 | TemplatePath: generateOpts.TemplatePath, 96 | CustomTypePackage: generateOpts.CustomTypePackage, 97 | FilenameSuffix: generateOpts.Suffix, 98 | SingleFile: generateOpts.SingleFile, 99 | Filename: generateOpts.Filename, 100 | FilenameUnderscore: generateOpts.FilenameUnderscore, 101 | Path: generateOpts.Path, 102 | }) 103 | if err := g.Generate(tableMap, ixMap); err != nil { 104 | return fmt.Errorf("error: %v", err) 105 | } 106 | 107 | return nil 108 | }, 109 | } 110 | ) 111 | 112 | func init() { 113 | generateCmd.Flags().BoolVar(&generateOpts.FromDDL, "from-ddl", false, "toggle using ddl file") 114 | setRootOpts(generateCmd, &generateOpts) 115 | rootCmd.AddCommand(generateCmd) 116 | } 117 | -------------------------------------------------------------------------------- /v2/test/benchmark/benchmark_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package test 21 | 22 | import ( 23 | "fmt" 24 | "testing" 25 | 26 | "cloud.google.com/go/spanner" 27 | "github.com/google/go-cmp/cmp" 28 | ) 29 | 30 | type BenchmarkModel struct { 31 | Col0 string `spanner:"Col0"` 32 | Col1 string `spanner:"Col1"` 33 | Col2 string `spanner:"Col2"` 34 | Col3 string `spanner:"Col3"` 35 | Col4 string `spanner:"Col4"` 36 | Col5 string `spanner:"Col5"` 37 | Col6 string `spanner:"Col6"` 38 | Col7 string `spanner:"Col7"` 39 | Col8 string `spanner:"Col8"` 40 | Col9 string `spanner:"Col9"` 41 | } 42 | 43 | var benchmarkModelColumns = []string{ 44 | "Col0", 45 | "Col1", 46 | "Col2", 47 | "Col3", 48 | "Col4", 49 | "Col5", 50 | "Col6", 51 | "Col7", 52 | "Col8", 53 | "Col9", 54 | } 55 | 56 | func (cpk *BenchmarkModel) columnsToPtrs(cols []string) ([]interface{}, error) { 57 | ret := make([]interface{}, 0, len(cols)) 58 | for _, col := range cols { 59 | switch col { 60 | case "Col0": 61 | ret = append(ret, &cpk.Col0) 62 | case "Col1": 63 | ret = append(ret, &cpk.Col1) 64 | case "Col2": 65 | ret = append(ret, &cpk.Col2) 66 | case "Col3": 67 | ret = append(ret, &cpk.Col3) 68 | case "Col4": 69 | ret = append(ret, &cpk.Col4) 70 | case "Col5": 71 | ret = append(ret, &cpk.Col5) 72 | case "Col6": 73 | ret = append(ret, &cpk.Col6) 74 | case "Col7": 75 | ret = append(ret, &cpk.Col7) 76 | case "Col8": 77 | ret = append(ret, &cpk.Col8) 78 | case "Col9": 79 | ret = append(ret, &cpk.Col9) 80 | 81 | default: 82 | return nil, fmt.Errorf("unknown column: %s", col) 83 | } 84 | } 85 | return ret, nil 86 | } 87 | 88 | func newBenchmarkModel_Decoder(cols []string) func(*spanner.Row) (*BenchmarkModel, error) { 89 | return func(row *spanner.Row) (*BenchmarkModel, error) { 90 | var cpk BenchmarkModel 91 | ptrs, err := cpk.columnsToPtrs(cols) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | if err := row.Columns(ptrs...); err != nil { 97 | return nil, err 98 | } 99 | 100 | return &cpk, nil 101 | } 102 | } 103 | 104 | func testRow(t testing.TB) *spanner.Row { 105 | var colVals []interface{} 106 | for i := 0; i < 10; i++ { 107 | colVals = append(colVals, fmt.Sprintf("val%d", i)) 108 | } 109 | row, err := spanner.NewRow(benchmarkModelColumns, colVals) 110 | if err != nil { 111 | t.Fatalf("new row: %v", err) 112 | } 113 | 114 | return row 115 | } 116 | 117 | func TestResult(t *testing.T) { 118 | row := testRow(t) 119 | 120 | var m1 BenchmarkModel 121 | if err := row.ToStruct(&m1); err != nil { 122 | t.Fatalf("error: %v", err) 123 | } 124 | 125 | decoder := newBenchmarkModel_Decoder(benchmarkModelColumns) 126 | m2, err := decoder(row) 127 | if err != nil { 128 | t.Fatalf("error: %v", err) 129 | } 130 | 131 | if diff := cmp.Diff(&m1, m2); diff != "" { 132 | t.Errorf("(-m1, +m2)\n%s", diff) 133 | } 134 | } 135 | 136 | func BenchmarkToStruct(b *testing.B) { 137 | row := testRow(b) 138 | models := make([]BenchmarkModel, b.N) 139 | 140 | b.ResetTimer() 141 | for i := 0; i < b.N; i++ { 142 | if err := row.ToStruct(&models[i]); err != nil { 143 | b.Fatalf("error: %v", err) 144 | } 145 | } 146 | } 147 | 148 | func BenchmarkDecoder(b *testing.B) { 149 | row := testRow(b) 150 | decoder := newBenchmarkModel_Decoder(benchmarkModelColumns) 151 | models := make([]*BenchmarkModel, b.N) 152 | 153 | b.ResetTimer() 154 | for i := 0; i < b.N; i++ { 155 | m, err := decoder(row) 156 | if err != nil { 157 | b.Fatalf("error: %v", err) 158 | } 159 | models[i] = m 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /v2/module/builtin/templates/index.go.tpl: -------------------------------------------------------------------------------- 1 | {{- range .Indexes }} 2 | {{- $short := (shortName .Type.Name "err" "sqlstr" "db" "q" "res" "YOLog" .Fields) -}} 3 | {{- $table := (.Type.TableName) -}} 4 | 5 | {{- if not .IsUnique }} 6 | // Find{{ .FuncName }} retrieves multiple rows from '{{ $table }}' as a slice of {{ .Type.Name }}. 7 | // 8 | // Generated from index '{{ .IndexName }}'. 9 | func Find{{ .FuncName }}(ctx context.Context, db YODB{{ goParams .Fields true true }}) ([]*{{ .Type.Name }}, error) { 10 | {{- else }} 11 | // Find{{ .FuncName }} retrieves a row from '{{ $table }}' as a {{ .Type.Name }}. 12 | // 13 | // If no row is present with the given key, then ReadRow returns an error where 14 | // spanner.ErrCode(err) is codes.NotFound. 15 | // 16 | // Generated from unique index '{{ .IndexName }}'. 17 | func Find{{ .FuncName }}(ctx context.Context, db YODB{{ goParams .Fields true true }}) (*{{ .Type.Name }}, error) { 18 | {{- end }} 19 | {{- if not .NullableFields }} 20 | const sqlstr = "SELECT " + 21 | "{{ columnNamesWithoutHidden .Type.Fields }} " + 22 | "FROM {{ $table }}@{FORCE_INDEX={{ .IndexName }}} " + 23 | "WHERE {{ columnNamesQuery .Fields " AND " }}" 24 | {{- else }} 25 | var sqlstr = "SELECT " + 26 | "{{ columnNamesWithoutHidden .Type.Fields }} " + 27 | "FROM {{ $table }}@{FORCE_INDEX={{ .IndexName }}} " 28 | 29 | conds := make([]string, {{ len .Fields }}) 30 | {{- range $i, $f := .Fields }} 31 | {{- if $f.IsNotNull }} 32 | conds[{{ $i }}] = "{{ escape $f.ColumnName }} = @param{{ $i }}" 33 | {{- else }} 34 | if {{ nullcheck $f }} { 35 | conds[{{ $i }}] = "{{ escape $f.ColumnName }} IS NULL" 36 | } else { 37 | conds[{{ $i }}] = "{{ escape $f.ColumnName }} = @param{{ $i }}" 38 | } 39 | {{- end }} 40 | {{- end }} 41 | sqlstr += "WHERE " + strings.Join(conds, " AND ") 42 | {{- end }} 43 | 44 | stmt := spanner.NewStatement(sqlstr) 45 | {{- range $i, $f := .Fields }} 46 | stmt.Params["param{{ $i }}"] = {{ goEncodedParam $f.Name }} 47 | {{- end}} 48 | 49 | 50 | decoder := new{{ .Type.Name }}_Decoder({{ .Type.Name }}Columns()) 51 | 52 | // run query 53 | YOLog(ctx, sqlstr{{ goParams .Fields true false }}) 54 | {{- if .IsUnique }} 55 | iter := db.Query(ctx, stmt) 56 | defer iter.Stop() 57 | 58 | row, err := iter.Next() 59 | if err != nil { 60 | if err == iterator.Done { 61 | return nil, newErrorWithCode(codes.NotFound, "Find{{ .FuncName }}", "{{ $table }}", err) 62 | } 63 | return nil, newError("Find{{ .FuncName }}", "{{ $table }}", err) 64 | } 65 | 66 | {{ $short }}, err := decoder(row) 67 | if err != nil { 68 | return nil, newErrorWithCode(codes.Internal, "Find{{ .FuncName }}", "{{ $table }}", err) 69 | } 70 | 71 | return {{ $short }}, nil 72 | {{- else }} 73 | iter := db.Query(ctx, stmt) 74 | defer iter.Stop() 75 | 76 | // load results 77 | res := []*{{ .Type.Name }}{} 78 | for { 79 | row, err := iter.Next() 80 | if err != nil { 81 | if err == iterator.Done { 82 | break 83 | } 84 | return nil, newError("Find{{ .FuncName }}", "{{ $table }}", err) 85 | } 86 | 87 | {{ $short }}, err := decoder(row) 88 | if err != nil { 89 | return nil, newErrorWithCode(codes.Internal, "Find{{ .FuncName }}", "{{ $table }}", err) 90 | } 91 | 92 | res = append(res, {{ $short }}) 93 | } 94 | 95 | return res, nil 96 | {{- end }} 97 | } 98 | 99 | 100 | // Read{{ .FuncName }} retrieves multiples rows from '{{ $table }}' by KeySet as a slice. 101 | // 102 | // This does not retrieve all columns of '{{ $table }}' because an index has only columns 103 | // used for primary key, index key and storing columns. If you need more columns, add storing 104 | // columns or Read by primary key or Query with join. 105 | // 106 | // Generated from index '{{ .IndexName }}'. 107 | func Read{{ .FuncName }}(ctx context.Context, db YODB, keys spanner.KeySet) ([]*{{ .Type.Name }}, error) { 108 | var res []*{{ .Type.Name }} 109 | columns := []string{ 110 | {{- range .Type.PrimaryKeyFields }} 111 | "{{ .ColumnName }}", 112 | {{- end }} 113 | {{- range .Fields }} 114 | "{{ .ColumnName }}", 115 | {{- end }} 116 | {{- range .StoringFields }} 117 | "{{ .ColumnName }}", 118 | {{- end }} 119 | } 120 | 121 | decoder := new{{ .Type.Name }}_Decoder(columns) 122 | 123 | rows := db.ReadUsingIndex(ctx, "{{ $table }}", "{{ .IndexName }}", keys, columns) 124 | err := rows.Do(func(row *spanner.Row) error { 125 | {{ $short }}, err := decoder(row) 126 | if err != nil { 127 | return err 128 | } 129 | res = append(res, {{ $short }}) 130 | 131 | return nil 132 | }) 133 | if err != nil { 134 | return nil, newErrorWithCode(codes.Internal, "Read{{ .FuncName }}", "{{ $table }}", err) 135 | } 136 | 137 | return res, nil 138 | } 139 | {{- end }} 140 | -------------------------------------------------------------------------------- /v2/module/builtin/templates/legacy_index.go.tpl: -------------------------------------------------------------------------------- 1 | {{- range .Indexes }} 2 | {{- $short := (shortName .Type.Name "err" "sqlstr" "db" "q" "res" "YOLog" .Fields) -}} 3 | {{- $table := (.Type.TableName) -}} 4 | 5 | {{- if not .IsUnique }} 6 | // Find{{ .LegacyFuncName }} retrieves multiple rows from '{{ $table }}' as a slice of {{ .Type.Name }}. 7 | // 8 | // Generated from index '{{ .IndexName }}'. 9 | func Find{{ .LegacyFuncName }}(ctx context.Context, db YODB{{ goParams .Fields true true }}) ([]*{{ .Type.Name }}, error) { 10 | {{- else }} 11 | // Find{{ .LegacyFuncName }} retrieves a row from '{{ $table }}' as a {{ .Type.Name }}. 12 | // 13 | // If no row is present with the given key, then ReadRow returns an error where 14 | // spanner.ErrCode(err) is codes.NotFound. 15 | // 16 | // Generated from unique index '{{ .IndexName }}'. 17 | func Find{{ .LegacyFuncName }}(ctx context.Context, db YODB{{ goParams .Fields true true }}) (*{{ .Type.Name }}, error) { 18 | {{- end }} 19 | {{- if not .NullableFields }} 20 | const sqlstr = "SELECT " + 21 | "{{ columnNames .Type.Fields }} " + 22 | "FROM {{ $table }}@{FORCE_INDEX={{ .IndexName }}} " + 23 | "WHERE {{ columnNamesQuery .Fields " AND " }}" 24 | {{- else }} 25 | var sqlstr = "SELECT " + 26 | "{{ columnNames .Type.Fields }} " + 27 | "FROM {{ $table }}@{FORCE_INDEX={{ .IndexName }}} " 28 | 29 | conds := make([]string, {{ len .Fields }}) 30 | {{- range $i, $f := .Fields }} 31 | {{- if $f.IsNotNull }} 32 | conds[{{ $i }}] = "{{ escape $f.ColumnName }} = @param{{ $i }}" 33 | {{- else }} 34 | if {{ nullcheck $f }} { 35 | conds[{{ $i }}] = "{{ escape $f.ColumnName }} IS NULL" 36 | } else { 37 | conds[{{ $i }}] = "{{ escape $f.ColumnName }} = @param{{ $i }}" 38 | } 39 | {{- end }} 40 | {{- end }} 41 | sqlstr += "WHERE " + strings.Join(conds, " AND ") 42 | {{- end }} 43 | 44 | stmt := spanner.NewStatement(sqlstr) 45 | {{- range $i, $f := .Fields }} 46 | stmt.Params["param{{ $i }}"] = {{ goEncodedParam $f.Name }} 47 | {{- end}} 48 | 49 | 50 | decoder := new{{ .Type.Name }}_Decoder({{ .Type.Name }}Columns()) 51 | 52 | // run query 53 | YOLog(ctx, sqlstr{{ goParams .Fields true false }}) 54 | {{- if .IsUnique }} 55 | iter := db.Query(ctx, stmt) 56 | defer iter.Stop() 57 | 58 | row, err := iter.Next() 59 | if err != nil { 60 | if err == iterator.Done { 61 | return nil, newErrorWithCode(codes.NotFound, "Find{{ .LegacyFuncName }}", "{{ $table }}", err) 62 | } 63 | return nil, newError("Find{{ .LegacyFuncName }}", "{{ $table }}", err) 64 | } 65 | 66 | {{ $short }}, err := decoder(row) 67 | if err != nil { 68 | return nil, newErrorWithCode(codes.Internal, "Find{{ .LegacyFuncName }}", "{{ $table }}", err) 69 | } 70 | 71 | return {{ $short }}, nil 72 | {{- else }} 73 | iter := db.Query(ctx, stmt) 74 | defer iter.Stop() 75 | 76 | // load results 77 | res := []*{{ .Type.Name }}{} 78 | for { 79 | row, err := iter.Next() 80 | if err != nil { 81 | if err == iterator.Done { 82 | break 83 | } 84 | return nil, newError("Find{{ .LegacyFuncName }}", "{{ $table }}", err) 85 | } 86 | 87 | {{ $short }}, err := decoder(row) 88 | if err != nil { 89 | return nil, newErrorWithCode(codes.Internal, "Find{{ .LegacyFuncName }}", "{{ $table }}", err) 90 | } 91 | 92 | res = append(res, {{ $short }}) 93 | } 94 | 95 | return res, nil 96 | {{- end }} 97 | } 98 | 99 | 100 | // Read{{ .LegacyFuncName }} retrieves multiples rows from '{{ $table }}' by KeySet as a slice. 101 | // 102 | // This does not retrieve all columns of '{{ $table }}' because an index has only columns 103 | // used for primary key, index key and storing columns. If you need more columns, add storing 104 | // columns or Read by primary key or Query with join. 105 | // 106 | // Generated from unique index '{{ .IndexName }}'. 107 | func Read{{ .LegacyFuncName }}(ctx context.Context, db YODB, keys spanner.KeySet) ([]*{{ .Type.Name }}, error) { 108 | var res []*{{ .Type.Name }} 109 | columns := []string{ 110 | {{- range .Type.PrimaryKeyFields }} 111 | "{{ .ColumnName }}", 112 | {{- end }} 113 | {{- range .Fields }} 114 | "{{ .ColumnName }}", 115 | {{- end }} 116 | {{- range .StoringFields }} 117 | "{{ .ColumnName }}", 118 | {{- end }} 119 | } 120 | 121 | decoder := new{{ .Type.Name }}_Decoder(columns) 122 | 123 | rows := db.ReadUsingIndex(ctx, "{{ $table }}", "{{ .IndexName }}", keys, columns) 124 | err := rows.Do(func(row *spanner.Row) error { 125 | {{ $short }}, err := decoder(row) 126 | if err != nil { 127 | return err 128 | } 129 | res = append(res, {{ $short }}) 130 | 131 | return nil 132 | }) 133 | if err != nil { 134 | return nil, newErrorWithCode(codes.Internal, "Read{{ .LegacyFuncName }}", "{{ $table }}", err) 135 | } 136 | 137 | return res, nil 138 | } 139 | {{- end }} 140 | -------------------------------------------------------------------------------- /v2/test/testmodels/default/yo_db.yo.go: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | 3 | // Package models contains the types. 4 | package models 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "fmt" 10 | "strconv" 11 | 12 | "cloud.google.com/go/spanner" 13 | "github.com/googleapis/gax-go/v2/apierror" 14 | "google.golang.org/grpc/codes" 15 | "google.golang.org/grpc/status" 16 | ) 17 | 18 | // YODB is the common interface for database operations. 19 | type YODB interface { 20 | ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error) 21 | Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator 22 | ReadUsingIndex(ctx context.Context, table, index string, keys spanner.KeySet, columns []string) (ri *spanner.RowIterator) 23 | Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator 24 | } 25 | 26 | // YOLog provides the log func used by generated queries. 27 | var YOLog = func(context.Context, string, ...interface{}) {} 28 | 29 | func newError(method, table string, err error) error { 30 | code := spanner.ErrCode(err) 31 | return newErrorWithCode(code, method, table, err) 32 | } 33 | 34 | func newErrorWithCode(code codes.Code, method, table string, err error) error { 35 | return &yoError{ 36 | method: method, 37 | table: table, 38 | err: err, 39 | code: code, 40 | } 41 | } 42 | 43 | type yoError struct { 44 | err error 45 | method string 46 | table string 47 | code codes.Code 48 | } 49 | 50 | func (e yoError) Error() string { 51 | return fmt.Sprintf("yo error in %s(%s): %v", e.method, e.table, e.err) 52 | } 53 | 54 | func (e yoError) Unwrap() error { 55 | return e.err 56 | } 57 | 58 | func (e yoError) DBTableName() string { 59 | return e.table 60 | } 61 | 62 | // GRPCStatus implements a conversion to a gRPC status using `status.Convert(error)`. 63 | // If the error is originated from the Spanner library, this returns a gRPC status of 64 | // the original error. It may contain details of the status such as RetryInfo. 65 | func (e yoError) GRPCStatus() *status.Status { 66 | var ae *apierror.APIError 67 | if errors.As(e.err, &ae) { 68 | return status.Convert(ae) 69 | } 70 | 71 | return status.New(e.code, e.Error()) 72 | } 73 | 74 | func (e yoError) Timeout() bool { return e.code == codes.DeadlineExceeded } 75 | func (e yoError) Temporary() bool { return e.code == codes.DeadlineExceeded } 76 | func (e yoError) NotFound() bool { return e.code == codes.NotFound } 77 | 78 | // yoEncode encodes primitive types that spanner library does not support into spanner types before 79 | // passing to spanner functions. Suppotted primitive types and user defined types that implement 80 | // spanner.Encoder interface are handled in encoding phase inside spanner libirary. 81 | func yoEncode(v interface{}) interface{} { 82 | switch vv := v.(type) { 83 | case int8: 84 | return int64(vv) 85 | case uint8: 86 | return int64(vv) 87 | case int16: 88 | return int64(vv) 89 | case uint16: 90 | return int64(vv) 91 | case int32: 92 | return int64(vv) 93 | case uint32: 94 | return int64(vv) 95 | case uint64: 96 | return int64(vv) 97 | default: 98 | return v 99 | } 100 | } 101 | 102 | // yoDecode wraps primitive types that spanner library does not support to decode from spanner types 103 | // by yoPrimitiveDecoder before passing to spanner functions. Supported primitive types and 104 | // user defined types that implement spanner.Decoder interface are handled in decoding phase inside 105 | // spanner libirary. 106 | func yoDecode(ptr interface{}) interface{} { 107 | switch ptr.(type) { 108 | case *int8, *uint8, *int16, *uint16, *int32, *uint32, *uint64: 109 | return &yoPrimitiveDecoder{val: ptr} 110 | default: 111 | return ptr 112 | } 113 | } 114 | 115 | type yoPrimitiveDecoder struct { 116 | val interface{} 117 | } 118 | 119 | func (y *yoPrimitiveDecoder) DecodeSpanner(val interface{}) error { 120 | strVal, ok := val.(string) 121 | if !ok { 122 | return spanner.ToSpannerError(status.Errorf(codes.FailedPrecondition, "failed to decode customField: %T(%v)", val, val)) 123 | } 124 | 125 | intVal, err := strconv.ParseInt(strVal, 10, 64) 126 | if err != nil { 127 | return spanner.ToSpannerError(status.Errorf(codes.FailedPrecondition, "%v wasn't correctly encoded: <%v>", val, err)) 128 | } 129 | 130 | switch vv := y.val.(type) { 131 | case *int8: 132 | *vv = int8(intVal) 133 | case *uint8: 134 | *vv = uint8(intVal) 135 | case *int16: 136 | *vv = int16(intVal) 137 | case *uint16: 138 | *vv = uint16(intVal) 139 | case *int32: 140 | *vv = int32(intVal) 141 | case *uint32: 142 | *vv = uint32(intVal) 143 | case *uint64: 144 | *vv = uint64(intVal) 145 | default: 146 | return status.Errorf(codes.Internal, "unexpected type for yoPrimitiveDecoder: %T", y.val) 147 | } 148 | 149 | return nil 150 | } 151 | -------------------------------------------------------------------------------- /v2/test/testmodels/legacy_default/yo_db.yo.go: -------------------------------------------------------------------------------- 1 | // Code generated by yo. DO NOT EDIT. 2 | 3 | // Package models contains the types. 4 | package models 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "fmt" 10 | "strconv" 11 | 12 | "cloud.google.com/go/spanner" 13 | "github.com/googleapis/gax-go/v2/apierror" 14 | "google.golang.org/grpc/codes" 15 | "google.golang.org/grpc/status" 16 | ) 17 | 18 | // YODB is the common interface for database operations. 19 | type YODB interface { 20 | ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error) 21 | Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator 22 | ReadUsingIndex(ctx context.Context, table, index string, keys spanner.KeySet, columns []string) (ri *spanner.RowIterator) 23 | Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator 24 | } 25 | 26 | // YOLog provides the log func used by generated queries. 27 | var YOLog = func(context.Context, string, ...interface{}) {} 28 | 29 | func newError(method, table string, err error) error { 30 | code := spanner.ErrCode(err) 31 | return newErrorWithCode(code, method, table, err) 32 | } 33 | 34 | func newErrorWithCode(code codes.Code, method, table string, err error) error { 35 | return &yoError{ 36 | method: method, 37 | table: table, 38 | err: err, 39 | code: code, 40 | } 41 | } 42 | 43 | type yoError struct { 44 | err error 45 | method string 46 | table string 47 | code codes.Code 48 | } 49 | 50 | func (e yoError) Error() string { 51 | return fmt.Sprintf("yo error in %s(%s): %v", e.method, e.table, e.err) 52 | } 53 | 54 | func (e yoError) Unwrap() error { 55 | return e.err 56 | } 57 | 58 | func (e yoError) DBTableName() string { 59 | return e.table 60 | } 61 | 62 | // GRPCStatus implements a conversion to a gRPC status using `status.Convert(error)`. 63 | // If the error is originated from the Spanner library, this returns a gRPC status of 64 | // the original error. It may contain details of the status such as RetryInfo. 65 | func (e yoError) GRPCStatus() *status.Status { 66 | var ae *apierror.APIError 67 | if errors.As(e.err, &ae) { 68 | return status.Convert(ae) 69 | } 70 | 71 | return status.New(e.code, e.Error()) 72 | } 73 | 74 | func (e yoError) Timeout() bool { return e.code == codes.DeadlineExceeded } 75 | func (e yoError) Temporary() bool { return e.code == codes.DeadlineExceeded } 76 | func (e yoError) NotFound() bool { return e.code == codes.NotFound } 77 | 78 | // yoEncode encodes primitive types that spanner library does not support into spanner types before 79 | // passing to spanner functions. Suppotted primitive types and user defined types that implement 80 | // spanner.Encoder interface are handled in encoding phase inside spanner libirary. 81 | func yoEncode(v interface{}) interface{} { 82 | switch vv := v.(type) { 83 | case int8: 84 | return int64(vv) 85 | case uint8: 86 | return int64(vv) 87 | case int16: 88 | return int64(vv) 89 | case uint16: 90 | return int64(vv) 91 | case int32: 92 | return int64(vv) 93 | case uint32: 94 | return int64(vv) 95 | case uint64: 96 | return int64(vv) 97 | default: 98 | return v 99 | } 100 | } 101 | 102 | // yoDecode wraps primitive types that spanner library does not support to decode from spanner types 103 | // by yoPrimitiveDecoder before passing to spanner functions. Supported primitive types and 104 | // user defined types that implement spanner.Decoder interface are handled in decoding phase inside 105 | // spanner libirary. 106 | func yoDecode(ptr interface{}) interface{} { 107 | switch ptr.(type) { 108 | case *int8, *uint8, *int16, *uint16, *int32, *uint32, *uint64: 109 | return &yoPrimitiveDecoder{val: ptr} 110 | default: 111 | return ptr 112 | } 113 | } 114 | 115 | type yoPrimitiveDecoder struct { 116 | val interface{} 117 | } 118 | 119 | func (y *yoPrimitiveDecoder) DecodeSpanner(val interface{}) error { 120 | strVal, ok := val.(string) 121 | if !ok { 122 | return spanner.ToSpannerError(status.Errorf(codes.FailedPrecondition, "failed to decode customField: %T(%v)", val, val)) 123 | } 124 | 125 | intVal, err := strconv.ParseInt(strVal, 10, 64) 126 | if err != nil { 127 | return spanner.ToSpannerError(status.Errorf(codes.FailedPrecondition, "%v wasn't correctly encoded: <%v>", val, err)) 128 | } 129 | 130 | switch vv := y.val.(type) { 131 | case *int8: 132 | *vv = int8(intVal) 133 | case *uint8: 134 | *vv = uint8(intVal) 135 | case *int16: 136 | *vv = int16(intVal) 137 | case *uint16: 138 | *vv = uint16(intVal) 139 | case *int32: 140 | *vv = int32(intVal) 141 | case *uint32: 142 | *vv = uint32(intVal) 143 | case *uint64: 144 | *vv = uint64(intVal) 145 | default: 146 | return status.Errorf(codes.Internal, "unexpected type for yoPrimitiveDecoder: %T", y.val) 147 | } 148 | 149 | return nil 150 | } 151 | -------------------------------------------------------------------------------- /templates/index.go.tpl: -------------------------------------------------------------------------------- 1 | {{- $short := (shortname .Type.Name "err" "sqlstr" "db" "q" "res" "YOLog" .Fields) -}} 2 | {{- $table := (.Type.Table.TableName) -}} 3 | {{- if not .Index.IsUnique }} 4 | // Find{{ .FuncName }} retrieves multiple rows from '{{ $table }}' as a slice of {{ .Type.Name }}. 5 | // 6 | // Generated from index '{{ .Index.IndexName }}'. 7 | func Find{{ .FuncName }}(ctx context.Context, db YORODB{{ gocustomparamlist .Fields true true }}) ([]*{{ .Type.Name }}, error) { 8 | {{- else }} 9 | // Find{{ .FuncName }} retrieves a row from '{{ $table }}' as a {{ .Type.Name }}. 10 | // 11 | // If no row is present with the given key, then ReadRow returns an error where 12 | // spanner.ErrCode(err) is codes.NotFound. 13 | // 14 | // Generated from unique index '{{ .Index.IndexName }}'. 15 | func Find{{ .FuncName }}(ctx context.Context, db YORODB{{ gocustomparamlist .Fields true true }}) (*{{ .Type.Name }}, error) { 16 | {{- end }} 17 | {{- if not .NullableFields }} 18 | const sqlstr = "SELECT " + 19 | "{{ escapedcolnames .Type.Fields }} " + 20 | "FROM {{ $table }}@{FORCE_INDEX={{ .Index.IndexName }}} " + 21 | "WHERE {{ colnamesquery .Fields " AND " }}" 22 | {{- else }} 23 | var sqlstr = "SELECT " + 24 | "{{ escapedcolnames .Type.Fields }} " + 25 | "FROM {{ $table }}@{FORCE_INDEX={{ .Index.IndexName }}} " 26 | 27 | conds := make([]string, {{ columncount .Fields }}) 28 | {{- range $i, $f := .Fields }} 29 | {{- if $f.Col.NotNull }} 30 | conds[{{ $i }}] = "{{ escapedcolname $f.Col }} = @param{{ $i }}" 31 | {{- else }} 32 | if {{ nullcheck $f }} { 33 | conds[{{ $i }}] = "{{ escapedcolname $f.Col }} IS NULL" 34 | } else { 35 | conds[{{ $i }}] = "{{ escapedcolname $f.Col }} = @param{{ $i }}" 36 | } 37 | {{- end }} 38 | {{- end }} 39 | sqlstr += "WHERE " + strings.Join(conds, " AND ") 40 | {{- end }} 41 | 42 | stmt := spanner.NewStatement(sqlstr) 43 | {{- range $i, $f := .Fields }} 44 | {{- if $f.CustomType }} 45 | stmt.Params["param{{ $i }}"] = {{ $f.Type }}({{ goparamname $f.Name }}) 46 | {{- else }} 47 | stmt.Params["param{{ $i }}"] = {{ goparamname $f.Name }} 48 | {{- end }} 49 | {{- end}} 50 | 51 | 52 | decoder := new{{ .Type.Name }}_Decoder({{ .Type.Name }}Columns()) 53 | 54 | // run query 55 | YOLog(ctx, sqlstr{{ goparamlist .Fields true false }}) 56 | {{- if .Index.IsUnique }} 57 | iter := db.Query(ctx, stmt) 58 | defer iter.Stop() 59 | 60 | row, err := iter.Next() 61 | if err != nil { 62 | if err == iterator.Done { 63 | return nil, newErrorWithCode(codes.NotFound, "Find{{ .FuncName }}", "{{ $table }}", err) 64 | } 65 | return nil, newError("Find{{ .FuncName }}", "{{ $table }}", err) 66 | } 67 | 68 | {{ $short }}, err := decoder(row) 69 | if err != nil { 70 | return nil, newErrorWithCode(codes.Internal, "Find{{ .FuncName }}", "{{ $table }}", err) 71 | } 72 | 73 | return {{ $short }}, nil 74 | {{- else }} 75 | iter := db.Query(ctx, stmt) 76 | defer iter.Stop() 77 | 78 | // load results 79 | res := []*{{ .Type.Name }}{} 80 | for { 81 | row, err := iter.Next() 82 | if err != nil { 83 | if err == iterator.Done { 84 | break 85 | } 86 | return nil, newError("Find{{ .FuncName }}", "{{ $table }}", err) 87 | } 88 | 89 | {{ $short }}, err := decoder(row) 90 | if err != nil { 91 | return nil, newErrorWithCode(codes.Internal, "Find{{ .FuncName }}", "{{ $table }}", err) 92 | } 93 | 94 | res = append(res, {{ $short }}) 95 | } 96 | 97 | return res, nil 98 | {{- end }} 99 | } 100 | 101 | 102 | // Read{{ .FuncName }} retrieves multiples rows from '{{ $table }}' by KeySet as a slice. 103 | // 104 | // This does not retrieve all columns of '{{ $table }}' because an index has only columns 105 | // used for primary key, index key and storing columns. If you need more columns, add storing 106 | // columns or Read by primary key or Query with join. 107 | // 108 | // Generated from unique index '{{ .Index.IndexName }}'. 109 | func Read{{ .FuncName }}(ctx context.Context, db YORODB, keys spanner.KeySet) ([]*{{ .Type.Name }}, error) { 110 | var res []*{{ .Type.Name }} 111 | columns := []string{ 112 | {{- range .Type.PrimaryKeyFields }} 113 | "{{ colname .Col }}", 114 | {{- end }} 115 | {{- range .Fields }} 116 | "{{ colname .Col }}", 117 | {{- end }} 118 | {{- range .StoringFields }} 119 | "{{ colname .Col }}", 120 | {{- end }} 121 | } 122 | 123 | decoder := new{{ .Type.Name }}_Decoder(columns) 124 | 125 | rows := db.ReadUsingIndex(ctx, "{{ $table }}", "{{ .Index.IndexName }}", keys, columns) 126 | err := rows.Do(func(row *spanner.Row) error { 127 | {{ $short }}, err := decoder(row) 128 | if err != nil { 129 | return err 130 | } 131 | res = append(res, {{ $short }}) 132 | 133 | return nil 134 | }) 135 | if err != nil { 136 | return nil, newErrorWithCode(codes.Internal, "Read{{ .FuncName }}", "{{ $table }}", err) 137 | } 138 | 139 | return res, nil 140 | } 141 | 142 | -------------------------------------------------------------------------------- /test/testdata/schema.sql: -------------------------------------------------------------------------------- 1 | -- Copyright (c) 2020 Mercari, Inc. 2 | -- 3 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | -- this software and associated documentation files (the "Software"), to deal in 5 | -- the Software without restriction, including without limitation the rights to 6 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | -- the Software, and to permit persons to whom the Software is furnished to do so, 8 | -- subject to the following conditions: 9 | -- 10 | -- The above copyright notice and this permission notice shall be included in all 11 | -- copies or substantial portions of the Software. 12 | -- 13 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | -- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | -- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | -- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | -- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | CREATE TABLE CompositePrimaryKeys ( 21 | Id INT64 NOT NULL, 22 | PKey1 STRING(32) NOT NULL, 23 | PKey2 INT64 NOT NULL, 24 | Error INT64 NOT NULL, 25 | X STRING(32) NOT NULL, 26 | Y STRING(32) NOT NULL, 27 | Z STRING(32) NOT NULL, 28 | ) PRIMARY KEY(PKey1, PKey2); 29 | 30 | CREATE INDEX CompositePrimaryKeysByXY ON CompositePrimaryKeys(X, Y); 31 | CREATE INDEX CompositePrimaryKeysByError ON CompositePrimaryKeys(Error); 32 | CREATE INDEX CompositePrimaryKeysByError2 ON CompositePrimaryKeys(Error) STORING(Z); 33 | CREATE INDEX CompositePrimaryKeysByError3 ON CompositePrimaryKeys(Error) STORING(Z, Y); 34 | -- CREATE INDEX ForTestCommentIndex ON CompositePrimaryKeys(X, Y, Z); 35 | 36 | CREATE TABLE OutOfOrderPrimaryKeys ( 37 | PKey1 STRING(32) NOT NULL, 38 | PKey2 STRING(32) NOT NULL, 39 | PKey3 STRING(32) NOT NULL, 40 | ) PRIMARY KEY(PKey2, PKey1, PKey3); 41 | 42 | CREATE TABLE FullTypes ( 43 | PKey STRING(32) NOT NULL, 44 | FTString STRING(32) NOT NULL, 45 | FTStringNull STRING(32), 46 | FTBool BOOL NOT NULL, 47 | FTBoolNull BOOL, 48 | FTBytes BYTES(32) NOT NULL, 49 | FTBytesNull BYTES(32), 50 | FTTimestamp TIMESTAMP NOT NULL, 51 | FTTimestampNull TIMESTAMP, 52 | FTInt INT64 NOT NULL, 53 | FTIntNull INT64, 54 | FTFloat FLOAT64 NOT NULL, 55 | FTFloatNull FLOAT64, 56 | FTDate DATE NOT NULL, 57 | FTDateNull DATE, 58 | FTJson JSON NOT NULL, 59 | FTJsonNull JSON, 60 | FTArrayStringNull ARRAY, 61 | FTArrayString ARRAY NOT NULL, 62 | FTArrayBoolNull ARRAY, 63 | FTArrayBool ARRAY NOT NULL, 64 | FTArrayBytesNull ARRAY, 65 | FTArrayBytes ARRAY NOT NULL, 66 | FTArrayTimestampNull ARRAY, 67 | FTArrayTimestamp ARRAY NOT NULL, 68 | FTArrayIntNull ARRAY, 69 | FTArrayInt ARRAY NOT NULL, 70 | FTArrayFloatNull ARRAY, 71 | FTArrayFloat ARRAY NOT NULL, 72 | FTArrayDateNull ARRAY, 73 | FTArrayDate ARRAY NOT NULL, 74 | FTArrayJsonNull ARRAY, 75 | FTArrayJson ARRAY NOT NULL, 76 | ) PRIMARY KEY(PKey); 77 | 78 | CREATE UNIQUE INDEX FullTypesByFTString ON FullTypes(FTString); 79 | 80 | CREATE INDEX FullTypesByIntDate ON FullTypes(FTInt, FTDate); 81 | 82 | CREATE INDEX FullTypesByIntTimestamp ON FullTypes(FTInt, FTTimestamp); 83 | 84 | CREATE INDEX FullTypesByInTimestampNull ON FullTypes(FTInt, FTTimestampNull); 85 | 86 | CREATE INDEX FullTypesByTimestamp ON FullTypes(FTTimestamp); 87 | 88 | CREATE TABLE MaxLengths ( 89 | MaxString STRING(MAX) NOT NULL, 90 | MaxBytes BYTES(MAX) NOT NULL, 91 | ) PRIMARY KEY(MaxString); 92 | 93 | CREATE TABLE snake_cases ( 94 | id INT64 NOT NULL, 95 | string_id STRING(32) NOT NULL, 96 | foo_bar_baz INT64 NOT NULL, 97 | ) PRIMARY KEY(id); 98 | 99 | CREATE INDEX snake_cases_by_string_id ON snake_cases(string_id, foo_bar_baz); 100 | 101 | CREATE TABLE Items ( 102 | ID INT64 NOT NULL, 103 | Price INT64 NOT NULL, 104 | ) PRIMARY KEY (ID); 105 | 106 | CREATE TABLE FereignItems ( 107 | ID INT64 NOT NULL, 108 | ItemID INT64 NOT NULL, 109 | Category INT64 NOT NULL, 110 | CONSTRAINT FK_ItemID_ForeignItems FOREIGN KEY (ItemID) REFERENCES Items (ID) 111 | ) PRIMARY KEY (ID); 112 | 113 | CREATE TABLE GeneratedColumns ( 114 | ID INT64 NOT NULL, 115 | FirstName STRING(50) NOT NULL, 116 | LastName STRING(50) NOT NULL, 117 | FullName STRING(100) NOT NULL AS (ARRAY_TO_STRING([FirstName, LastName], " ")) STORED, 118 | ) PRIMARY KEY (ID); 119 | 120 | CREATE TABLE AllowCommitTimestamp ( 121 | ID INT64 NOT NULL, 122 | UpdatedAt TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp=true) 123 | ) PRIMARY KEY(ID); 124 | 125 | CREATE TABLE FullTextSearch ( 126 | ID INT64 NOT NULL, 127 | Content STRING(2048) NOT NULL, 128 | Content_Tokens TOKENLIST AS (TOKENIZE_FULLTEXT(Content)) HIDDEN, 129 | ) PRIMARY KEY(ID); 130 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export SPANNER_EMULATOR_HOST ?= localhost:9010 2 | export SPANNER_EMULATOR_HOST_REST ?= localhost:9020 3 | export SPANNER_PROJECT_NAME ?= yo-test 4 | export SPANNER_INSTANCE_NAME ?= yo-test 5 | export SPANNER_DATABASE_NAME ?= yo-test 6 | 7 | YOBIN ?= yo 8 | 9 | .PHONY: help 10 | help: ## show this help message. 11 | @grep -hE '^\S+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 12 | 13 | all: build 14 | 15 | build: regen ## build yo command and regenerate template bin 16 | go build 17 | 18 | regen: tplbin/templates.go ## regenerate template bin 19 | 20 | deps: 21 | go install github.com/jessevdk/go-assets-builder@latest 22 | 23 | .PHONY: gomod 24 | gomod: ## Run go mod 25 | go mod tidy 26 | cd v2; go mod tidy 27 | 28 | .PHONY: lint 29 | lint: ## Run linters 30 | go fmt ./... 31 | go vet ./... 32 | 33 | tplbin/templates.go: $(wildcard templates/*.tpl) 34 | go-assets-builder \ 35 | --package=tplbin \ 36 | --strip-prefix="/templates/" \ 37 | --output tplbin/templates.go \ 38 | templates/*.tpl 39 | 40 | .PHONY: test 41 | test: ## run test 42 | @echo run tests with spanner emulator 43 | go test -race -v ./test 44 | 45 | testdata: ## generate test models 46 | $(MAKE) -j4 testdata/default testdata/underscore testdata/customtypes testdata/single 47 | 48 | testdata/default: 49 | rm -rf test/testmodels/default && mkdir -p test/testmodels/default 50 | $(YOBIN) $(SPANNER_PROJECT_NAME) $(SPANNER_INSTANCE_NAME) $(SPANNER_DATABASE_NAME) --package models --out test/testmodels/default/ 51 | 52 | testdata/underscore: 53 | rm -rf test/testmodels/underscore && mkdir -p test/testmodels/underscore 54 | $(YOBIN) $(SPANNER_PROJECT_NAME) $(SPANNER_INSTANCE_NAME) $(SPANNER_DATABASE_NAME) --package models --underscore --out test/testmodels/underscore/ 55 | 56 | testdata/single: 57 | rm -rf test/testmodels/single && mkdir -p test/testmodels/single 58 | $(YOBIN) $(SPANNER_PROJECT_NAME) $(SPANNER_INSTANCE_NAME) $(SPANNER_DATABASE_NAME) --out test/testmodels/single/single_file.go --single-file 59 | 60 | testdata/customtypes: 61 | rm -rf test/testmodels/customtypes && mkdir -p test/testmodels/customtypes 62 | $(YOBIN) $(SPANNER_PROJECT_NAME) $(SPANNER_INSTANCE_NAME) $(SPANNER_DATABASE_NAME) --custom-types-file test/testdata/custom_column_types.yml --out test/testmodels/customtypes/ 63 | 64 | testdata-from-ddl: 65 | $(MAKE) -j4 testdata-from-ddl/default testdata-from-ddl/underscore testdata-from-ddl/customtypes testdata-from-ddl/single 66 | 67 | testdata-from-ddl/default: 68 | rm -rf test/testmodels/default && mkdir -p test/testmodels/default 69 | $(YOBIN) generate ./test/testdata/schema.sql --from-ddl --package models --out test/testmodels/default/ 70 | 71 | testdata-from-ddl/underscore: 72 | rm -rf test/testmodels/underscore && mkdir -p test/testmodels/underscore 73 | $(YOBIN) generate ./test/testdata/schema.sql --from-ddl --package models --underscore --out test/testmodels/underscore/ 74 | 75 | testdata-from-ddl/single: 76 | rm -rf test/testmodels/single && mkdir -p test/testmodels/single 77 | $(YOBIN) generate ./test/testdata/schema.sql --from-ddl --out test/testmodels/single/single_file.go --single-file 78 | 79 | testdata-from-ddl/customtypes: 80 | rm -rf test/testmodels/customtypes && mkdir -p test/testmodels/customtypes 81 | $(YOBIN) generate ./test/testdata/schema.sql --from-ddl --custom-types-file test/testdata/custom_column_types.yml --out test/testmodels/customtypes/ 82 | 83 | recreate-templates:: ## recreate templates 84 | rm -rf templates && mkdir templates 85 | $(YOBIN) create-template --template-path templates 86 | 87 | .PHONY: check_lint 88 | check_lint: lint ## check linter errors 89 | if git diff --quiet; then \ 90 | exit 0; \ 91 | else \ 92 | echo "\nerror: make lint resulted in a change of files."; \ 93 | echo "Please run make lint locally before pushing."; \ 94 | exit 1; \ 95 | fi 96 | 97 | .PHONY: check_gomod 98 | check_gomod: gomod ## check whether or not go mod tidy has been run 99 | if git diff --quiet go.mod go.sum v2/go.mod v2/go.sum; then \ 100 | exit 0; \ 101 | else \ 102 | echo "\nerror: make gomod resulted in a change of files."; \ 103 | echo "Please run make gomod locally before pushing."; \ 104 | exit 1; \ 105 | fi 106 | 107 | .PHONY: check-diff 108 | 109 | EXPECTED_FILES := \ 110 | test/testmodels/customtypes/compositeprimarykey.yo.go \ 111 | test/testmodels/default/compositeprimarykey.yo.go \ 112 | test/testmodels/single/single_file.go \ 113 | test/testmodels/underscore/composite_primary_key.yo.go 114 | 115 | check-diff: 116 | @echo "Checking git diff against expected files..." 117 | @ACTUAL_FILES=$$(git diff --name-only | grep -v '^go\.mod$$' | grep -v '^go\.sum$$' | sort) ; \ 118 | SORTED_EXPECTED_FILES=$$(echo "$(EXPECTED_FILES)" | tr ' ' '\n' | sort) ; \ 119 | if [ "$$ACTUAL_FILES" = "$$SORTED_EXPECTED_FILES" ]; then \ 120 | echo "Success: git diff output matches the expected file list." ; \ 121 | else \ 122 | echo "Error: git diff output does not match the expected file list." ; \ 123 | echo "--- Expected Files ---" ; \ 124 | echo "$$SORTED_EXPECTED_FILES" ; \ 125 | echo "--- Actual Files ---" ; \ 126 | echo "$$ACTUAL_FILES" ; \ 127 | exit 1 ; \ 128 | fi 129 | -------------------------------------------------------------------------------- /internal/reserved_keywords.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Mercari, Inc. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | package internal 21 | 22 | // This list was created with reference to https://cloud.google.com/spanner/docs/lexical#reserved-keywords 23 | var reservedKeywords = map[string]struct{}{ 24 | "ALL": struct{}{}, 25 | "AND": struct{}{}, 26 | "ANY": struct{}{}, 27 | "ARRAY": struct{}{}, 28 | "AS": struct{}{}, 29 | "ASC": struct{}{}, 30 | "ASSERT_ROWS_MODIFIED": struct{}{}, 31 | "AT": struct{}{}, 32 | "BETWEEN": struct{}{}, 33 | "BY": struct{}{}, 34 | "CASE": struct{}{}, 35 | "CAST": struct{}{}, 36 | "COLLATE": struct{}{}, 37 | "CONTAINS": struct{}{}, 38 | "CREATE": struct{}{}, 39 | "CROSS": struct{}{}, 40 | "CUBE": struct{}{}, 41 | "CURRENT": struct{}{}, 42 | "DEFAULT": struct{}{}, 43 | "DEFINE": struct{}{}, 44 | "DESC": struct{}{}, 45 | "DISTINCT": struct{}{}, 46 | "ELSE": struct{}{}, 47 | "END": struct{}{}, 48 | "ENUM": struct{}{}, 49 | "ESCAPE": struct{}{}, 50 | "EXCEPT": struct{}{}, 51 | "EXCLUDE": struct{}{}, 52 | "EXISTS": struct{}{}, 53 | "EXTRACT": struct{}{}, 54 | "FALSE": struct{}{}, 55 | "FETCH": struct{}{}, 56 | "FOLLOWING": struct{}{}, 57 | "FOR": struct{}{}, 58 | "FROM": struct{}{}, 59 | "FULL": struct{}{}, 60 | "GROUP": struct{}{}, 61 | "GROUPING": struct{}{}, 62 | "GROUPS": struct{}{}, 63 | "HASH": struct{}{}, 64 | "HAVING": struct{}{}, 65 | "IF": struct{}{}, 66 | "IGNORE": struct{}{}, 67 | "IN": struct{}{}, 68 | "INNER": struct{}{}, 69 | "INTERSECT": struct{}{}, 70 | "INTERVAL": struct{}{}, 71 | "INTO": struct{}{}, 72 | "IS": struct{}{}, 73 | "JOIN": struct{}{}, 74 | "LATERAL": struct{}{}, 75 | "LEFT": struct{}{}, 76 | "LIKE": struct{}{}, 77 | "LIMIT": struct{}{}, 78 | "LOOKUP": struct{}{}, 79 | "MERGE": struct{}{}, 80 | "NATURAL": struct{}{}, 81 | "NEW": struct{}{}, 82 | "NO": struct{}{}, 83 | "NOT": struct{}{}, 84 | "NULL": struct{}{}, 85 | "NULLS": struct{}{}, 86 | "OF": struct{}{}, 87 | "ON": struct{}{}, 88 | "OR": struct{}{}, 89 | "ORDER": struct{}{}, 90 | "OUTER": struct{}{}, 91 | "OVER": struct{}{}, 92 | "PARTITION": struct{}{}, 93 | "PRECEDING": struct{}{}, 94 | "PROTO": struct{}{}, 95 | "RANGE": struct{}{}, 96 | "RECURSIVE": struct{}{}, 97 | "RESPECT": struct{}{}, 98 | "RIGHT": struct{}{}, 99 | "ROLLUP": struct{}{}, 100 | "ROWS": struct{}{}, 101 | "SELECT": struct{}{}, 102 | "SET": struct{}{}, 103 | "SOME": struct{}{}, 104 | "STRUCT": struct{}{}, 105 | "TABLESAMPLE": struct{}{}, 106 | "THEN": struct{}{}, 107 | "TO": struct{}{}, 108 | "TREAT": struct{}{}, 109 | "TRUE": struct{}{}, 110 | "UNBOUNDED": struct{}{}, 111 | "UNION": struct{}{}, 112 | "UNNEST": struct{}{}, 113 | "USING": struct{}{}, 114 | "WHEN": struct{}{}, 115 | "WHERE": struct{}{}, 116 | "WINDOW": struct{}{}, 117 | "WITH": struct{}{}, 118 | "WITHIN": struct{}{}, 119 | } 120 | --------------------------------------------------------------------------------