├── 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 |
--------------------------------------------------------------------------------