snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
This commit is contained in:
14
internal/codegen/golang/driver.go
Normal file
14
internal/codegen/golang/driver.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package golang
|
||||
|
||||
import "github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
|
||||
|
||||
func parseDriver(sqlPackage string) opts.SQLDriver {
|
||||
switch sqlPackage {
|
||||
case opts.SQLPackagePGXV4:
|
||||
return opts.SQLDriverPGXV4
|
||||
case opts.SQLPackagePGXV5:
|
||||
return opts.SQLDriverPGXV5
|
||||
default:
|
||||
return opts.SQLDriverLibPQ
|
||||
}
|
||||
}
|
||||
64
internal/codegen/golang/enum.go
Normal file
64
internal/codegen/golang/enum.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type Constant struct {
|
||||
Name string
|
||||
Type string
|
||||
Value string
|
||||
}
|
||||
|
||||
type Enum struct {
|
||||
Name string
|
||||
Comment string
|
||||
Constants []Constant
|
||||
NameTags map[string]string
|
||||
ValidTags map[string]string
|
||||
}
|
||||
|
||||
func (e Enum) NameTag() string {
|
||||
return TagsToString(e.NameTags)
|
||||
}
|
||||
|
||||
func (e Enum) ValidTag() string {
|
||||
return TagsToString(e.ValidTags)
|
||||
}
|
||||
|
||||
func enumReplacer(r rune) rune {
|
||||
if strings.ContainsRune("-/:_", r) {
|
||||
return '_'
|
||||
} else if (r >= 'a' && r <= 'z') ||
|
||||
(r >= 'A' && r <= 'Z') ||
|
||||
(r >= '0' && r <= '9') {
|
||||
return r
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// EnumReplace removes all non ident symbols (all but letters, numbers and
|
||||
// underscore) and returns valid ident name for provided name.
|
||||
func EnumReplace(value string) string {
|
||||
return strings.Map(enumReplacer, value)
|
||||
}
|
||||
|
||||
// EnumValueName removes all non ident symbols (all but letters, numbers and
|
||||
// underscore) and converts snake case ident to camel case.
|
||||
func EnumValueName(value string) string {
|
||||
parts := strings.Split(EnumReplace(value), "_")
|
||||
for i, part := range parts {
|
||||
parts[i] = titleFirst(part)
|
||||
}
|
||||
|
||||
return strings.Join(parts, "")
|
||||
}
|
||||
|
||||
func titleFirst(s string) string {
|
||||
r := []rune(s)
|
||||
r[0] = unicode.ToUpper(r[0])
|
||||
|
||||
return string(r)
|
||||
}
|
||||
143
internal/codegen/golang/field.go
Normal file
143
internal/codegen/golang/field.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
type Field struct {
|
||||
Name string // CamelCased name for Go
|
||||
DBName string // Name as used in the DB
|
||||
Type string
|
||||
Tags map[string]string
|
||||
Comment string
|
||||
Column *plugin.Column
|
||||
// EmbedFields contains the embedded fields that require scanning.
|
||||
EmbedFields []Field
|
||||
}
|
||||
|
||||
func (gf Field) Tag() string {
|
||||
return TagsToString(gf.Tags)
|
||||
}
|
||||
|
||||
func (gf Field) HasSqlcSlice() bool {
|
||||
return gf.Column.IsSqlcSlice
|
||||
}
|
||||
|
||||
func TagsToString(tags map[string]string) string {
|
||||
if len(tags) == 0 {
|
||||
return ""
|
||||
}
|
||||
tagParts := make([]string, 0, len(tags))
|
||||
for key, val := range tags {
|
||||
tagParts = append(tagParts, fmt.Sprintf("%s:%q", key, val))
|
||||
}
|
||||
sort.Strings(tagParts)
|
||||
return strings.Join(tagParts, " ")
|
||||
}
|
||||
|
||||
func JSONTagName(name string, options *opts.Options) string {
|
||||
style := options.JsonTagsCaseStyle
|
||||
idUppercase := options.JsonTagsIdUppercase
|
||||
if style == "" || style == "none" {
|
||||
return name
|
||||
} else {
|
||||
return SetJSONCaseStyle(name, style, idUppercase)
|
||||
}
|
||||
}
|
||||
|
||||
func SetCaseStyle(name string, style string) string {
|
||||
switch style {
|
||||
case "camel":
|
||||
return toCamelCase(name)
|
||||
case "pascal":
|
||||
return toPascalCase(name)
|
||||
case "snake":
|
||||
return toSnakeCase(name)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported JSON tags case style: '%s'", style))
|
||||
}
|
||||
}
|
||||
|
||||
func SetJSONCaseStyle(name string, style string, idUppercase bool) string {
|
||||
switch style {
|
||||
case "camel":
|
||||
return toJsonCamelCase(name, idUppercase)
|
||||
case "pascal":
|
||||
return toPascalCase(name)
|
||||
case "snake":
|
||||
return toSnakeCase(name)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported JSON tags case style: '%s'", style))
|
||||
}
|
||||
}
|
||||
|
||||
var camelPattern = regexp.MustCompile("[^A-Z][A-Z]+")
|
||||
|
||||
func toSnakeCase(s string) string {
|
||||
if !strings.ContainsRune(s, '_') {
|
||||
s = camelPattern.ReplaceAllStringFunc(s, func(x string) string {
|
||||
return x[:1] + "_" + x[1:]
|
||||
})
|
||||
}
|
||||
return strings.ToLower(s)
|
||||
}
|
||||
|
||||
func toCamelCase(s string) string {
|
||||
return toCamelInitCase(s, false)
|
||||
}
|
||||
|
||||
func toPascalCase(s string) string {
|
||||
return toCamelInitCase(s, true)
|
||||
}
|
||||
|
||||
func toCamelInitCase(name string, initUpper bool) string {
|
||||
var out strings.Builder
|
||||
for i, p := range strings.Split(name, "_") {
|
||||
if !initUpper && i == 0 {
|
||||
out.WriteString(p)
|
||||
continue
|
||||
}
|
||||
if p == "id" {
|
||||
out.WriteString("ID")
|
||||
} else {
|
||||
out.WriteString(strings.Title(p))
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func toJsonCamelCase(name string, idUppercase bool) string {
|
||||
var out strings.Builder
|
||||
idStr := "Id"
|
||||
|
||||
if idUppercase {
|
||||
idStr = "ID"
|
||||
}
|
||||
|
||||
for i, p := range strings.Split(name, "_") {
|
||||
if i == 0 {
|
||||
out.WriteString(p)
|
||||
continue
|
||||
}
|
||||
if p == "id" {
|
||||
out.WriteString(idStr)
|
||||
} else {
|
||||
out.WriteString(strings.Title(p))
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func toLowerCase(str string) string {
|
||||
if str == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.ToLower(str[:1]) + str[1:]
|
||||
}
|
||||
426
internal/codegen/golang/gen.go
Normal file
426
internal/codegen/golang/gen.go
Normal file
@@ -0,0 +1,426 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/format"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/sdk"
|
||||
"github.com/sqlc-dev/sqlc/internal/metadata"
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
type tmplCtx struct {
|
||||
Q string
|
||||
Package string
|
||||
ModelsPackage string
|
||||
SQLDriver opts.SQLDriver
|
||||
Enums []Enum
|
||||
Structs []Struct
|
||||
GoQueries []Query
|
||||
SqlcVersion string
|
||||
|
||||
// TODO: Race conditions
|
||||
SourceName string
|
||||
|
||||
EmitJSONTags bool
|
||||
JsonTagsIDUppercase bool
|
||||
EmitDBTags bool
|
||||
EmitPreparedQueries bool
|
||||
EmitInterface bool
|
||||
EmitEmptySlices bool
|
||||
EmitMethodsWithDBArgument bool
|
||||
EmitEnumValidMethod bool
|
||||
EmitAllEnumValues bool
|
||||
UsesCopyFrom bool
|
||||
UsesBatch bool
|
||||
OmitSqlcVersion bool
|
||||
BuildTags string
|
||||
WrapErrors bool
|
||||
}
|
||||
|
||||
func (t *tmplCtx) OutputQuery(sourceName string) bool {
|
||||
return t.SourceName == sourceName
|
||||
}
|
||||
|
||||
func (t *tmplCtx) codegenDbarg() string {
|
||||
if t.EmitMethodsWithDBArgument {
|
||||
return "db DBTX, "
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Called as a global method since subtemplate queryCodeStdExec does not have
|
||||
// access to the toplevel tmplCtx
|
||||
func (t *tmplCtx) codegenEmitPreparedQueries() bool {
|
||||
return t.EmitPreparedQueries
|
||||
}
|
||||
|
||||
func (t *tmplCtx) codegenQueryMethod(q Query) string {
|
||||
db := "q.db"
|
||||
if t.EmitMethodsWithDBArgument {
|
||||
db = "db"
|
||||
}
|
||||
|
||||
switch q.Cmd {
|
||||
case ":one":
|
||||
if t.EmitPreparedQueries {
|
||||
return "q.queryRow"
|
||||
}
|
||||
return db + ".QueryRowContext"
|
||||
|
||||
case ":many":
|
||||
if t.EmitPreparedQueries {
|
||||
return "q.query"
|
||||
}
|
||||
return db + ".QueryContext"
|
||||
|
||||
default:
|
||||
if t.EmitPreparedQueries {
|
||||
return "q.exec"
|
||||
}
|
||||
return db + ".ExecContext"
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tmplCtx) codegenQueryRetval(q Query) (string, error) {
|
||||
switch q.Cmd {
|
||||
case ":one":
|
||||
return "row :=", nil
|
||||
case ":many":
|
||||
return "rows, err :=", nil
|
||||
case ":exec":
|
||||
return "_, err :=", nil
|
||||
case ":execrows", ":execlastid":
|
||||
return "result, err :=", nil
|
||||
case ":execresult":
|
||||
if t.WrapErrors {
|
||||
return "result, err :=", nil
|
||||
}
|
||||
return "return", nil
|
||||
default:
|
||||
return "", fmt.Errorf("unhandled q.Cmd case %q", q.Cmd)
|
||||
}
|
||||
}
|
||||
|
||||
func Generate(ctx context.Context, req *plugin.GenerateRequest) (*plugin.GenerateResponse, error) {
|
||||
options, err := opts.Parse(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := opts.ValidateOpts(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enums := buildEnums(req, options)
|
||||
structs := buildStructs(req, options)
|
||||
queries, err := buildQueries(req, options, enums, structs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.OmitUnusedStructs {
|
||||
enums, structs = filterUnusedStructs(enums, structs, queries, options.ModelsTypeQualifier())
|
||||
}
|
||||
|
||||
if err := validate(options, enums, structs, queries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return generate(req, options, enums, structs, queries)
|
||||
}
|
||||
|
||||
func validate(options *opts.Options, enums []Enum, structs []Struct, queries []Query) error {
|
||||
enumNames := make(map[string]struct{})
|
||||
for _, enum := range enums {
|
||||
enumNames[enum.Name] = struct{}{}
|
||||
enumNames["Null"+enum.Name] = struct{}{}
|
||||
}
|
||||
structNames := make(map[string]struct{})
|
||||
for _, struckt := range structs {
|
||||
if _, ok := enumNames[struckt.Name]; ok {
|
||||
return fmt.Errorf("struct name conflicts with enum name: %s", struckt.Name)
|
||||
}
|
||||
structNames[struckt.Name] = struct{}{}
|
||||
}
|
||||
if !options.EmitExportedQueries {
|
||||
return nil
|
||||
}
|
||||
for _, query := range queries {
|
||||
if _, ok := enumNames[query.ConstantName]; ok {
|
||||
return fmt.Errorf("query constant name conflicts with enum name: %s", query.ConstantName)
|
||||
}
|
||||
if _, ok := structNames[query.ConstantName]; ok {
|
||||
return fmt.Errorf("query constant name conflicts with struct name: %s", query.ConstantName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum, structs []Struct, queries []Query) (*plugin.GenerateResponse, error) {
|
||||
i := &importer{
|
||||
Options: options,
|
||||
Queries: queries,
|
||||
Enums: enums,
|
||||
Structs: structs,
|
||||
}
|
||||
|
||||
tctx := tmplCtx{
|
||||
EmitInterface: options.EmitInterface,
|
||||
EmitJSONTags: options.EmitJsonTags,
|
||||
JsonTagsIDUppercase: options.JsonTagsIdUppercase,
|
||||
EmitDBTags: options.EmitDbTags,
|
||||
EmitPreparedQueries: options.EmitPreparedQueries,
|
||||
EmitEmptySlices: options.EmitEmptySlices,
|
||||
EmitMethodsWithDBArgument: options.EmitMethodsWithDbArgument,
|
||||
EmitEnumValidMethod: options.EmitEnumValidMethod,
|
||||
EmitAllEnumValues: options.EmitAllEnumValues,
|
||||
UsesCopyFrom: usesCopyFrom(queries),
|
||||
UsesBatch: usesBatch(queries),
|
||||
SQLDriver: parseDriver(options.SqlPackage),
|
||||
Q: "`",
|
||||
Package: options.Package,
|
||||
ModelsPackage: options.ModelsPackage(),
|
||||
Enums: enums,
|
||||
Structs: structs,
|
||||
SqlcVersion: req.SqlcVersion,
|
||||
BuildTags: options.BuildTags,
|
||||
OmitSqlcVersion: options.OmitSqlcVersion,
|
||||
WrapErrors: options.WrapErrors,
|
||||
}
|
||||
|
||||
if tctx.UsesCopyFrom && !tctx.SQLDriver.IsPGX() && options.SqlDriver != opts.SQLDriverGoSQLDriverMySQL {
|
||||
return nil, errors.New(":copyfrom is only supported by pgx and github.com/go-sql-driver/mysql")
|
||||
}
|
||||
|
||||
if tctx.UsesCopyFrom && options.SqlDriver == opts.SQLDriverGoSQLDriverMySQL {
|
||||
if err := checkNoTimesForMySQLCopyFrom(queries); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tctx.SQLDriver = opts.SQLDriverGoSQLDriverMySQL
|
||||
}
|
||||
|
||||
if tctx.UsesBatch && !tctx.SQLDriver.IsPGX() {
|
||||
return nil, errors.New(":batch* commands are only supported by pgx")
|
||||
}
|
||||
|
||||
funcMap := template.FuncMap{
|
||||
"lowerTitle": sdk.LowerTitle,
|
||||
"comment": sdk.DoubleSlashComment,
|
||||
"escape": sdk.EscapeBacktick,
|
||||
"imports": i.Imports,
|
||||
"hasImports": i.HasImports,
|
||||
"hasPrefix": strings.HasPrefix,
|
||||
|
||||
// These methods are Go specific, they do not belong in the codegen package
|
||||
// (as that is language independent)
|
||||
"dbarg": tctx.codegenDbarg,
|
||||
"emitPreparedQueries": tctx.codegenEmitPreparedQueries,
|
||||
"queryMethod": tctx.codegenQueryMethod,
|
||||
"queryRetval": tctx.codegenQueryRetval,
|
||||
}
|
||||
|
||||
tmpl := template.Must(
|
||||
template.New("table").
|
||||
Funcs(funcMap).
|
||||
ParseFS(
|
||||
templates,
|
||||
"templates/*.tmpl",
|
||||
"templates/*/*.tmpl",
|
||||
),
|
||||
)
|
||||
|
||||
output := map[string]string{}
|
||||
|
||||
execute := func(name, templateName string) error {
|
||||
imports := i.Imports(name)
|
||||
replacedQueries := replaceConflictedArg(imports, queries)
|
||||
|
||||
var b bytes.Buffer
|
||||
w := bufio.NewWriter(&b)
|
||||
tctx.SourceName = name
|
||||
tctx.GoQueries = replacedQueries
|
||||
err := tmpl.ExecuteTemplate(w, templateName, &tctx)
|
||||
w.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
code, err := format.Source(b.Bytes())
|
||||
if err != nil {
|
||||
fmt.Println(b.String())
|
||||
return fmt.Errorf("source error: %w", err)
|
||||
}
|
||||
|
||||
if templateName == "queryFile" && options.OutputFilesSuffix != "" {
|
||||
name += options.OutputFilesSuffix
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(name, ".go") {
|
||||
name += ".go"
|
||||
}
|
||||
output[name] = string(code)
|
||||
return nil
|
||||
}
|
||||
|
||||
dbFileName := "db.go"
|
||||
if options.OutputDbFileName != "" {
|
||||
dbFileName = options.OutputDbFileName
|
||||
}
|
||||
modelsFileName := "models.go"
|
||||
if options.OutputModelsFileName != "" {
|
||||
modelsFileName = options.OutputModelsFileName
|
||||
}
|
||||
querierFileName := "querier.go"
|
||||
if options.OutputQuerierFileName != "" {
|
||||
querierFileName = options.OutputQuerierFileName
|
||||
}
|
||||
copyfromFileName := "copyfrom.go"
|
||||
if options.OutputCopyfromFileName != "" {
|
||||
copyfromFileName = options.OutputCopyfromFileName
|
||||
}
|
||||
|
||||
batchFileName := "batch.go"
|
||||
if options.OutputBatchFileName != "" {
|
||||
batchFileName = options.OutputBatchFileName
|
||||
}
|
||||
|
||||
if err := execute(dbFileName, "dbFile"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if options.ModelsEmitEnabled() {
|
||||
if err := execute(modelsFileName, "modelsFile"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if options.EmitInterface {
|
||||
if err := execute(querierFileName, "interfaceFile"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if tctx.UsesCopyFrom {
|
||||
if err := execute(copyfromFileName, "copyfromFile"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if tctx.UsesBatch {
|
||||
if err := execute(batchFileName, "batchFile"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
files := map[string]struct{}{}
|
||||
for _, gq := range queries {
|
||||
files[gq.SourceName] = struct{}{}
|
||||
}
|
||||
|
||||
for source := range files {
|
||||
if err := execute(source, "queryFile"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
resp := plugin.GenerateResponse{}
|
||||
|
||||
for filename, code := range output {
|
||||
resp.Files = append(resp.Files, &plugin.File{
|
||||
Name: filename,
|
||||
Contents: []byte(code),
|
||||
})
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func usesCopyFrom(queries []Query) bool {
|
||||
for _, q := range queries {
|
||||
if q.Cmd == metadata.CmdCopyFrom {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func usesBatch(queries []Query) bool {
|
||||
for _, q := range queries {
|
||||
if slices.Contains([]string{metadata.CmdBatchExec, metadata.CmdBatchMany, metadata.CmdBatchOne}, q.Cmd) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkNoTimesForMySQLCopyFrom(queries []Query) error {
|
||||
for _, q := range queries {
|
||||
if q.Cmd != metadata.CmdCopyFrom {
|
||||
continue
|
||||
}
|
||||
for _, f := range q.Arg.CopyFromMySQLFields() {
|
||||
if f.Type == "time.Time" {
|
||||
return fmt.Errorf("values with a timezone are not yet supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func filterUnusedStructs(enums []Enum, structs []Struct, queries []Query, qualifier string) ([]Enum, []Struct) {
|
||||
keepTypes := make(map[string]struct{})
|
||||
|
||||
keep := func(t string) {
|
||||
keepTypes[t] = struct{}{}
|
||||
// Also store the bare type name so that lookups against
|
||||
// bare struct/enum names match even when types have been
|
||||
// qualified with the models package prefix (e.g. "model.User").
|
||||
if bare := stripQualifier(t, qualifier); bare != t {
|
||||
keepTypes[bare] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for _, query := range queries {
|
||||
if !query.Arg.isEmpty() {
|
||||
keep(query.Arg.Type())
|
||||
if query.Arg.IsStruct() {
|
||||
for _, field := range query.Arg.Struct.Fields {
|
||||
keep(field.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
if query.hasRetType() {
|
||||
keep(query.Ret.Type())
|
||||
if query.Ret.IsStruct() {
|
||||
for _, field := range query.Ret.Struct.Fields {
|
||||
keep(strings.TrimPrefix(field.Type, "[]"))
|
||||
for _, embedField := range field.EmbedFields {
|
||||
keep(embedField.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keepEnums := make([]Enum, 0, len(enums))
|
||||
for _, enum := range enums {
|
||||
_, keep := keepTypes[enum.Name]
|
||||
_, keepNull := keepTypes["Null"+enum.Name]
|
||||
_, keepPointer := keepTypes["*"+enum.Name]
|
||||
if keep || keepNull || keepPointer {
|
||||
keepEnums = append(keepEnums, enum)
|
||||
}
|
||||
}
|
||||
|
||||
keepStructs := make([]Struct, 0, len(structs))
|
||||
for _, st := range structs {
|
||||
if _, ok := keepTypes[st.Name]; ok {
|
||||
keepStructs = append(keepStructs, st)
|
||||
}
|
||||
}
|
||||
|
||||
return keepEnums, keepStructs
|
||||
}
|
||||
92
internal/codegen/golang/go_type.go
Normal file
92
internal/codegen/golang/go_type.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"strings"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/sdk"
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
func addExtraGoStructTags(tags map[string]string, req *plugin.GenerateRequest, options *opts.Options, col *plugin.Column) {
|
||||
for _, override := range options.Overrides {
|
||||
oride := override.ShimOverride
|
||||
if oride.GoType.StructTags == nil {
|
||||
continue
|
||||
}
|
||||
if override.MatchesColumn(col) {
|
||||
maps.Copy(tags, oride.GoType.StructTags)
|
||||
continue
|
||||
}
|
||||
if !override.Matches(col.Table, req.Catalog.DefaultSchema) {
|
||||
// Different table.
|
||||
continue
|
||||
}
|
||||
cname := col.Name
|
||||
if col.OriginalName != "" {
|
||||
cname = col.OriginalName
|
||||
}
|
||||
if !sdk.MatchString(oride.ColumnName, cname) {
|
||||
// Different column.
|
||||
continue
|
||||
}
|
||||
// Add the extra tags.
|
||||
maps.Copy(tags, oride.GoType.StructTags)
|
||||
}
|
||||
}
|
||||
|
||||
func goType(req *plugin.GenerateRequest, options *opts.Options, col *plugin.Column) string {
|
||||
// Check if the column's type has been overridden
|
||||
for _, override := range options.Overrides {
|
||||
oride := override.ShimOverride
|
||||
|
||||
if oride.GoType.TypeName == "" {
|
||||
continue
|
||||
}
|
||||
cname := col.Name
|
||||
if col.OriginalName != "" {
|
||||
cname = col.OriginalName
|
||||
}
|
||||
sameTable := override.Matches(col.Table, req.Catalog.DefaultSchema)
|
||||
if oride.Column != "" && sdk.MatchString(oride.ColumnName, cname) && sameTable {
|
||||
if col.IsSqlcSlice {
|
||||
return "[]" + oride.GoType.TypeName
|
||||
}
|
||||
return oride.GoType.TypeName
|
||||
}
|
||||
}
|
||||
typ := goInnerType(req, options, col)
|
||||
if col.IsSqlcSlice {
|
||||
return "[]" + typ
|
||||
}
|
||||
if col.IsArray {
|
||||
return strings.Repeat("[]", int(col.ArrayDims)) + typ
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
func goInnerType(req *plugin.GenerateRequest, options *opts.Options, col *plugin.Column) string {
|
||||
// package overrides have a higher precedence
|
||||
for _, override := range options.Overrides {
|
||||
oride := override.ShimOverride
|
||||
if oride.GoType.TypeName == "" {
|
||||
continue
|
||||
}
|
||||
if override.MatchesColumn(col) {
|
||||
return oride.GoType.TypeName
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Extend the engine interface to handle types
|
||||
switch req.Settings.Engine {
|
||||
case "mysql":
|
||||
return mysqlType(req, options, col)
|
||||
case "postgresql":
|
||||
return postgresType(req, options, col)
|
||||
case "sqlite":
|
||||
return sqliteType(req, options, col)
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
}
|
||||
537
internal/codegen/golang/imports.go
Normal file
537
internal/codegen/golang/imports.go
Normal file
@@ -0,0 +1,537 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
|
||||
"github.com/sqlc-dev/sqlc/internal/metadata"
|
||||
)
|
||||
|
||||
type fileImports struct {
|
||||
Std []ImportSpec
|
||||
Dep []ImportSpec
|
||||
}
|
||||
|
||||
type ImportSpec struct {
|
||||
ID string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (s ImportSpec) String() string {
|
||||
if s.ID != "" {
|
||||
return fmt.Sprintf("%s %q", s.ID, s.Path)
|
||||
} else {
|
||||
return fmt.Sprintf("%q", s.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func mergeImports(imps ...fileImports) [][]ImportSpec {
|
||||
if len(imps) == 1 {
|
||||
return [][]ImportSpec{
|
||||
imps[0].Std,
|
||||
imps[0].Dep,
|
||||
}
|
||||
}
|
||||
|
||||
var stds, pkgs []ImportSpec
|
||||
seenStd := map[string]struct{}{}
|
||||
seenPkg := map[string]struct{}{}
|
||||
for i := range imps {
|
||||
for _, spec := range imps[i].Std {
|
||||
if _, ok := seenStd[spec.Path]; ok {
|
||||
continue
|
||||
}
|
||||
stds = append(stds, spec)
|
||||
seenStd[spec.Path] = struct{}{}
|
||||
}
|
||||
for _, spec := range imps[i].Dep {
|
||||
if _, ok := seenPkg[spec.Path]; ok {
|
||||
continue
|
||||
}
|
||||
pkgs = append(pkgs, spec)
|
||||
seenPkg[spec.Path] = struct{}{}
|
||||
}
|
||||
}
|
||||
return [][]ImportSpec{stds, pkgs}
|
||||
}
|
||||
|
||||
type importer struct {
|
||||
Options *opts.Options
|
||||
Queries []Query
|
||||
Enums []Enum
|
||||
Structs []Struct
|
||||
}
|
||||
|
||||
func (i *importer) usesType(typ string) bool {
|
||||
for _, strct := range i.Structs {
|
||||
for _, f := range strct.Fields {
|
||||
if hasPrefixIgnoringSliceAndPointerPrefix(f.Type, typ) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (i *importer) HasImports(filename string) bool {
|
||||
imports := i.Imports(filename)
|
||||
return len(imports[0]) != 0 || len(imports[1]) != 0
|
||||
}
|
||||
|
||||
func (i *importer) Imports(filename string) [][]ImportSpec {
|
||||
dbFileName := "db.go"
|
||||
if i.Options.OutputDbFileName != "" {
|
||||
dbFileName = i.Options.OutputDbFileName
|
||||
}
|
||||
modelsFileName := "models.go"
|
||||
if i.Options.OutputModelsFileName != "" {
|
||||
modelsFileName = i.Options.OutputModelsFileName
|
||||
}
|
||||
querierFileName := "querier.go"
|
||||
if i.Options.OutputQuerierFileName != "" {
|
||||
querierFileName = i.Options.OutputQuerierFileName
|
||||
}
|
||||
copyfromFileName := "copyfrom.go"
|
||||
if i.Options.OutputCopyfromFileName != "" {
|
||||
copyfromFileName = i.Options.OutputCopyfromFileName
|
||||
}
|
||||
batchFileName := "batch.go"
|
||||
if i.Options.OutputBatchFileName != "" {
|
||||
batchFileName = i.Options.OutputBatchFileName
|
||||
}
|
||||
|
||||
switch filename {
|
||||
case dbFileName:
|
||||
return mergeImports(i.dbImports())
|
||||
case modelsFileName:
|
||||
return mergeImports(i.modelImports())
|
||||
case querierFileName:
|
||||
return mergeImports(i.interfaceImports())
|
||||
case copyfromFileName:
|
||||
return mergeImports(i.copyfromImports())
|
||||
case batchFileName:
|
||||
return mergeImports(i.batchImports())
|
||||
default:
|
||||
return mergeImports(i.queryImports(filename))
|
||||
}
|
||||
}
|
||||
|
||||
func (i *importer) dbImports() fileImports {
|
||||
var pkg []ImportSpec
|
||||
std := []ImportSpec{
|
||||
{Path: "context"},
|
||||
}
|
||||
|
||||
sqlpkg := parseDriver(i.Options.SqlPackage)
|
||||
switch sqlpkg {
|
||||
case opts.SQLDriverPGXV4:
|
||||
pkg = append(pkg, ImportSpec{Path: "github.com/jackc/pgconn"})
|
||||
pkg = append(pkg, ImportSpec{Path: "github.com/jackc/pgx/v4"})
|
||||
case opts.SQLDriverPGXV5:
|
||||
pkg = append(pkg, ImportSpec{Path: "github.com/jackc/pgx/v5/pgconn"})
|
||||
pkg = append(pkg, ImportSpec{Path: "github.com/jackc/pgx/v5"})
|
||||
default:
|
||||
std = append(std, ImportSpec{Path: "database/sql"})
|
||||
if i.Options.EmitPreparedQueries {
|
||||
std = append(std, ImportSpec{Path: "fmt"})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(std, func(i, j int) bool { return std[i].Path < std[j].Path })
|
||||
sort.Slice(pkg, func(i, j int) bool { return pkg[i].Path < pkg[j].Path })
|
||||
return fileImports{Std: std, Dep: pkg}
|
||||
}
|
||||
|
||||
var stdlibTypes = map[string]string{
|
||||
"json.RawMessage": "encoding/json",
|
||||
"time.Time": "time",
|
||||
"net.IP": "net",
|
||||
"net.HardwareAddr": "net",
|
||||
"netip.Addr": "net/netip",
|
||||
"netip.Prefix": "net/netip",
|
||||
}
|
||||
|
||||
var pqtypeTypes = map[string]struct{}{
|
||||
"pqtype.CIDR": {},
|
||||
"pqtype.Inet": {},
|
||||
"pqtype.Macaddr": {},
|
||||
"pqtype.NullRawMessage": {},
|
||||
}
|
||||
|
||||
func buildImports(options *opts.Options, queries []Query, uses func(string) bool) (map[string]struct{}, map[ImportSpec]struct{}) {
|
||||
pkg := make(map[ImportSpec]struct{})
|
||||
std := make(map[string]struct{})
|
||||
|
||||
if uses("sql.Null") {
|
||||
std["database/sql"] = struct{}{}
|
||||
}
|
||||
|
||||
sqlpkg := parseDriver(options.SqlPackage)
|
||||
for _, q := range queries {
|
||||
if q.Cmd == metadata.CmdExecResult {
|
||||
switch sqlpkg {
|
||||
case opts.SQLDriverPGXV4:
|
||||
pkg[ImportSpec{Path: "github.com/jackc/pgconn"}] = struct{}{}
|
||||
case opts.SQLDriverPGXV5:
|
||||
pkg[ImportSpec{Path: "github.com/jackc/pgx/v5/pgconn"}] = struct{}{}
|
||||
default:
|
||||
std["database/sql"] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for typeName, pkg := range stdlibTypes {
|
||||
if uses(typeName) {
|
||||
std[pkg] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if uses("pgtype.") {
|
||||
if sqlpkg == opts.SQLDriverPGXV5 {
|
||||
pkg[ImportSpec{Path: "github.com/jackc/pgx/v5/pgtype"}] = struct{}{}
|
||||
} else {
|
||||
pkg[ImportSpec{Path: "github.com/jackc/pgtype"}] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for typeName := range pqtypeTypes {
|
||||
if uses(typeName) {
|
||||
pkg[ImportSpec{Path: "github.com/sqlc-dev/pqtype"}] = struct{}{}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
overrideTypes := map[string]string{}
|
||||
for _, override := range options.Overrides {
|
||||
o := override.ShimOverride
|
||||
if o.GoType.BasicType || o.GoType.TypeName == "" {
|
||||
continue
|
||||
}
|
||||
overrideTypes[o.GoType.TypeName] = o.GoType.ImportPath
|
||||
}
|
||||
|
||||
_, overrideNullTime := overrideTypes["pq.NullTime"]
|
||||
if uses("pq.NullTime") && !overrideNullTime {
|
||||
pkg[ImportSpec{Path: "github.com/lib/pq"}] = struct{}{}
|
||||
}
|
||||
_, overrideUUID := overrideTypes["uuid.UUID"]
|
||||
if uses("uuid.UUID") && !overrideUUID {
|
||||
pkg[ImportSpec{Path: "github.com/google/uuid"}] = struct{}{}
|
||||
}
|
||||
_, overrideNullUUID := overrideTypes["uuid.NullUUID"]
|
||||
if uses("uuid.NullUUID") && !overrideNullUUID {
|
||||
pkg[ImportSpec{Path: "github.com/google/uuid"}] = struct{}{}
|
||||
}
|
||||
_, overrideVector := overrideTypes["pgvector.Vector"]
|
||||
if uses("pgvector.Vector") && !overrideVector {
|
||||
pkg[ImportSpec{Path: "github.com/pgvector/pgvector-go"}] = struct{}{}
|
||||
}
|
||||
|
||||
// Custom imports
|
||||
for _, override := range options.Overrides {
|
||||
o := override.ShimOverride
|
||||
|
||||
if o.GoType.BasicType || o.GoType.TypeName == "" {
|
||||
continue
|
||||
}
|
||||
_, alreadyImported := std[o.GoType.ImportPath]
|
||||
hasPackageAlias := o.GoType.Package != ""
|
||||
if (!alreadyImported || hasPackageAlias) && uses(o.GoType.TypeName) {
|
||||
pkg[ImportSpec{Path: o.GoType.ImportPath, ID: o.GoType.Package}] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Models package import. When models live in a separate Go package and
|
||||
// any type in this file references a qualified model type, import the
|
||||
// models package under a fixed `models` alias so query files always
|
||||
// reference types as `models.User` regardless of how the actual
|
||||
// package is named.
|
||||
if options.ModelsAreExternal() {
|
||||
if uses(options.ModelsTypeQualifier()) {
|
||||
pkg[ImportSpec{Path: options.OutputModelsImport, ID: opts.ModelsImportAlias}] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return std, pkg
|
||||
}
|
||||
|
||||
func (i *importer) interfaceImports() fileImports {
|
||||
std, pkg := buildImports(i.Options, i.Queries, func(name string) bool {
|
||||
for _, q := range i.Queries {
|
||||
if q.hasRetType() {
|
||||
if usesBatch([]Query{q}) {
|
||||
continue
|
||||
}
|
||||
if hasPrefixIgnoringSliceAndPointerPrefix(q.Ret.Type(), name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
for _, f := range q.Arg.Pairs() {
|
||||
if hasPrefixIgnoringSliceAndPointerPrefix(f.Type, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
std["context"] = struct{}{}
|
||||
|
||||
return sortedImports(std, pkg)
|
||||
}
|
||||
|
||||
func (i *importer) modelImports() fileImports {
|
||||
std, pkg := buildImports(i.Options, nil, i.usesType)
|
||||
|
||||
if len(i.Enums) > 0 {
|
||||
std["fmt"] = struct{}{}
|
||||
std["database/sql/driver"] = struct{}{}
|
||||
}
|
||||
|
||||
return sortedImports(std, pkg)
|
||||
}
|
||||
|
||||
func sortedImports(std map[string]struct{}, pkg map[ImportSpec]struct{}) fileImports {
|
||||
pkgs := make([]ImportSpec, 0, len(pkg))
|
||||
for spec := range pkg {
|
||||
pkgs = append(pkgs, spec)
|
||||
}
|
||||
stds := make([]ImportSpec, 0, len(std))
|
||||
for path := range std {
|
||||
stds = append(stds, ImportSpec{Path: path})
|
||||
}
|
||||
sort.Slice(stds, func(i, j int) bool { return stds[i].Path < stds[j].Path })
|
||||
sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].Path < pkgs[j].Path })
|
||||
return fileImports{stds, pkgs}
|
||||
}
|
||||
|
||||
func (i *importer) queryImports(filename string) fileImports {
|
||||
var gq []Query
|
||||
anyNonCopyFrom := false
|
||||
for _, query := range i.Queries {
|
||||
if usesBatch([]Query{query}) {
|
||||
continue
|
||||
}
|
||||
if query.SourceName == filename {
|
||||
gq = append(gq, query)
|
||||
if query.Cmd != metadata.CmdCopyFrom {
|
||||
anyNonCopyFrom = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std, pkg := buildImports(i.Options, gq, func(name string) bool {
|
||||
for _, q := range gq {
|
||||
if q.hasRetType() {
|
||||
if q.Ret.EmitStruct() {
|
||||
for _, f := range q.Ret.Struct.Fields {
|
||||
if hasPrefixIgnoringSliceAndPointerPrefix(f.Type, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasPrefixIgnoringSliceAndPointerPrefix(q.Ret.Type(), name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// Check the fields of the argument struct if it's emitted
|
||||
if q.Arg.EmitStruct() {
|
||||
for _, f := range q.Arg.Struct.Fields {
|
||||
if hasPrefixIgnoringSliceAndPointerPrefix(f.Type, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check the argument pairs inside the method definition
|
||||
for _, f := range q.Arg.Pairs() {
|
||||
if hasPrefixIgnoringSliceAndPointerPrefix(f.Type, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
sliceScan := func() bool {
|
||||
for _, q := range gq {
|
||||
if q.hasRetType() {
|
||||
if q.Ret.IsStruct() {
|
||||
for _, f := range q.Ret.Struct.Fields {
|
||||
if strings.HasPrefix(f.Type, "[]") && f.Type != "[]byte" {
|
||||
return true
|
||||
}
|
||||
for _, embed := range f.EmbedFields {
|
||||
if strings.HasPrefix(embed.Type, "[]") && embed.Type != "[]byte" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if strings.HasPrefix(q.Ret.Type(), "[]") && q.Ret.Type() != "[]byte" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if !q.Arg.isEmpty() {
|
||||
if q.Arg.IsStruct() {
|
||||
for _, f := range q.Arg.Struct.Fields {
|
||||
if strings.HasPrefix(f.Type, "[]") && f.Type != "[]byte" && !f.HasSqlcSlice() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if strings.HasPrefix(q.Arg.Type(), "[]") && q.Arg.Type() != "[]byte" && !q.Arg.HasSqlcSlices() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Search for sqlc.slice() calls
|
||||
sqlcSliceScan := func() bool {
|
||||
for _, q := range gq {
|
||||
if q.Arg.HasSqlcSlices() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if anyNonCopyFrom {
|
||||
std["context"] = struct{}{}
|
||||
}
|
||||
|
||||
sqlpkg := parseDriver(i.Options.SqlPackage)
|
||||
if sqlcSliceScan() && !sqlpkg.IsPGX() {
|
||||
std["strings"] = struct{}{}
|
||||
}
|
||||
if sliceScan() && !sqlpkg.IsPGX() {
|
||||
pkg[ImportSpec{Path: "github.com/lib/pq"}] = struct{}{}
|
||||
}
|
||||
|
||||
if i.Options.WrapErrors {
|
||||
std["fmt"] = struct{}{}
|
||||
}
|
||||
|
||||
return sortedImports(std, pkg)
|
||||
}
|
||||
|
||||
func (i *importer) copyfromImports() fileImports {
|
||||
copyFromQueries := make([]Query, 0, len(i.Queries))
|
||||
for _, q := range i.Queries {
|
||||
if q.Cmd == metadata.CmdCopyFrom {
|
||||
copyFromQueries = append(copyFromQueries, q)
|
||||
}
|
||||
}
|
||||
std, pkg := buildImports(i.Options, copyFromQueries, func(name string) bool {
|
||||
for _, q := range copyFromQueries {
|
||||
if q.hasRetType() {
|
||||
if strings.HasPrefix(q.Ret.Type(), name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if !q.Arg.isEmpty() {
|
||||
if strings.HasPrefix(q.Arg.Type(), name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
std["context"] = struct{}{}
|
||||
if i.Options.SqlDriver == opts.SQLDriverGoSQLDriverMySQL {
|
||||
std["io"] = struct{}{}
|
||||
std["fmt"] = struct{}{}
|
||||
std["sync/atomic"] = struct{}{}
|
||||
pkg[ImportSpec{Path: "github.com/go-sql-driver/mysql"}] = struct{}{}
|
||||
pkg[ImportSpec{Path: "github.com/hexon/mysqltsv"}] = struct{}{}
|
||||
}
|
||||
|
||||
return sortedImports(std, pkg)
|
||||
}
|
||||
|
||||
func (i *importer) batchImports() fileImports {
|
||||
batchQueries := make([]Query, 0, len(i.Queries))
|
||||
for _, q := range i.Queries {
|
||||
if usesBatch([]Query{q}) {
|
||||
batchQueries = append(batchQueries, q)
|
||||
}
|
||||
}
|
||||
std, pkg := buildImports(i.Options, batchQueries, func(name string) bool {
|
||||
for _, q := range batchQueries {
|
||||
if q.hasRetType() {
|
||||
if q.Ret.EmitStruct() {
|
||||
for _, f := range q.Ret.Struct.Fields {
|
||||
if hasPrefixIgnoringSliceAndPointerPrefix(f.Type, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
if hasPrefixIgnoringSliceAndPointerPrefix(q.Ret.Type(), name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if q.Arg.EmitStruct() {
|
||||
for _, f := range q.Arg.Struct.Fields {
|
||||
if hasPrefixIgnoringSliceAndPointerPrefix(f.Type, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, f := range q.Arg.Pairs() {
|
||||
if hasPrefixIgnoringSliceAndPointerPrefix(f.Type, name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
std["context"] = struct{}{}
|
||||
std["errors"] = struct{}{}
|
||||
sqlpkg := parseDriver(i.Options.SqlPackage)
|
||||
switch sqlpkg {
|
||||
case opts.SQLDriverPGXV4:
|
||||
pkg[ImportSpec{Path: "github.com/jackc/pgx/v4"}] = struct{}{}
|
||||
case opts.SQLDriverPGXV5:
|
||||
pkg[ImportSpec{Path: "github.com/jackc/pgx/v5"}] = struct{}{}
|
||||
}
|
||||
|
||||
return sortedImports(std, pkg)
|
||||
}
|
||||
|
||||
func trimSliceAndPointerPrefix(v string) string {
|
||||
v = strings.TrimPrefix(v, "[]")
|
||||
v = strings.TrimPrefix(v, "*")
|
||||
return v
|
||||
}
|
||||
|
||||
func hasPrefixIgnoringSliceAndPointerPrefix(s, prefix string) bool {
|
||||
trimmedS := trimSliceAndPointerPrefix(s)
|
||||
trimmedPrefix := trimSliceAndPointerPrefix(prefix)
|
||||
return strings.HasPrefix(trimmedS, trimmedPrefix)
|
||||
}
|
||||
|
||||
func replaceConflictedArg(imports [][]ImportSpec, queries []Query) []Query {
|
||||
m := make(map[string]struct{})
|
||||
for _, is := range imports {
|
||||
for _, i := range is {
|
||||
paths := strings.Split(i.Path, "/")
|
||||
m[paths[len(paths)-1]] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
replacedQueries := make([]Query, 0, len(queries))
|
||||
for _, query := range queries {
|
||||
if _, exist := m[query.Arg.Name]; exist {
|
||||
query.Arg.Name = toCamelCase(fmt.Sprintf("arg_%s", query.Arg.Name))
|
||||
}
|
||||
replacedQueries = append(replacedQueries, query)
|
||||
}
|
||||
return replacedQueries
|
||||
}
|
||||
144
internal/codegen/golang/mysql_type.go
Normal file
144
internal/codegen/golang/mysql_type.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/sdk"
|
||||
"github.com/sqlc-dev/sqlc/internal/debug"
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
func mysqlType(req *plugin.GenerateRequest, options *opts.Options, col *plugin.Column) string {
|
||||
columnType := sdk.DataType(col.Type)
|
||||
notNull := col.NotNull || col.IsArray
|
||||
unsigned := col.Unsigned
|
||||
|
||||
switch columnType {
|
||||
|
||||
case "varchar", "text", "char", "tinytext", "mediumtext", "longtext":
|
||||
if notNull {
|
||||
return "string"
|
||||
}
|
||||
return "sql.NullString"
|
||||
|
||||
case "tinyint":
|
||||
if col.Length == 1 {
|
||||
if notNull {
|
||||
return "bool"
|
||||
}
|
||||
return "sql.NullBool"
|
||||
} else {
|
||||
if notNull {
|
||||
if unsigned {
|
||||
return "uint8"
|
||||
}
|
||||
return "int8"
|
||||
}
|
||||
// The database/sql package does not have a sql.NullInt8 type, so we
|
||||
// use the smallest type they have which is NullInt16
|
||||
return "sql.NullInt16"
|
||||
}
|
||||
|
||||
case "year":
|
||||
if notNull {
|
||||
return "int16"
|
||||
}
|
||||
return "sql.NullInt16"
|
||||
|
||||
case "smallint":
|
||||
if notNull {
|
||||
if unsigned {
|
||||
return "uint16"
|
||||
}
|
||||
return "int16"
|
||||
}
|
||||
return "sql.NullInt16"
|
||||
|
||||
case "int", "integer", "mediumint":
|
||||
if notNull {
|
||||
if unsigned {
|
||||
return "uint32"
|
||||
}
|
||||
return "int32"
|
||||
}
|
||||
return "sql.NullInt32"
|
||||
|
||||
case "bigint", "bigint unsigned", "bigint signed":
|
||||
// "bigint unsigned" and "bigint signed" are MySQL CAST types
|
||||
// Note: We use int64 for CAST AS UNSIGNED to match original behavior,
|
||||
// even though uint64 would be more semantically correct.
|
||||
// The Unsigned flag on columns (from table schema) still uses uint64.
|
||||
if notNull {
|
||||
if unsigned {
|
||||
return "uint64"
|
||||
}
|
||||
return "int64"
|
||||
}
|
||||
return "sql.NullInt64"
|
||||
|
||||
case "blob", "binary", "varbinary", "tinyblob", "mediumblob", "longblob":
|
||||
if notNull {
|
||||
return "[]byte"
|
||||
}
|
||||
return "sql.NullString"
|
||||
|
||||
case "double", "double precision", "real", "float":
|
||||
if notNull {
|
||||
return "float64"
|
||||
}
|
||||
return "sql.NullFloat64"
|
||||
|
||||
case "decimal", "dec", "fixed":
|
||||
if notNull {
|
||||
return "string"
|
||||
}
|
||||
return "sql.NullString"
|
||||
|
||||
case "enum":
|
||||
// TODO: Proper Enum support
|
||||
return "string"
|
||||
|
||||
case "date", "timestamp", "datetime", "time":
|
||||
if notNull {
|
||||
return "time.Time"
|
||||
}
|
||||
return "sql.NullTime"
|
||||
|
||||
case "boolean", "bool":
|
||||
if notNull {
|
||||
return "bool"
|
||||
}
|
||||
return "sql.NullBool"
|
||||
|
||||
case "json":
|
||||
return "json.RawMessage"
|
||||
|
||||
case "any":
|
||||
return "any"
|
||||
|
||||
default:
|
||||
for _, schema := range req.Catalog.Schemas {
|
||||
for _, enum := range schema.Enums {
|
||||
if enum.Name == columnType {
|
||||
if notNull {
|
||||
if schema.Name == req.Catalog.DefaultSchema {
|
||||
return StructName(enum.Name, options)
|
||||
}
|
||||
return StructName(schema.Name+"_"+enum.Name, options)
|
||||
} else {
|
||||
if schema.Name == req.Catalog.DefaultSchema {
|
||||
return "Null" + StructName(enum.Name, options)
|
||||
}
|
||||
return "Null" + StructName(schema.Name+"_"+enum.Name, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if debug.Active {
|
||||
log.Printf("Unknown MySQL type: %s\n", columnType)
|
||||
}
|
||||
return "any"
|
||||
|
||||
}
|
||||
}
|
||||
64
internal/codegen/golang/opts/enum.go
Normal file
64
internal/codegen/golang/opts/enum.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package opts
|
||||
|
||||
import "fmt"
|
||||
|
||||
type SQLDriver string
|
||||
|
||||
const (
|
||||
SQLPackagePGXV4 string = "pgx/v4"
|
||||
SQLPackagePGXV5 string = "pgx/v5"
|
||||
SQLPackageStandard string = "database/sql"
|
||||
)
|
||||
|
||||
var validPackages = map[string]struct{}{
|
||||
string(SQLPackagePGXV4): {},
|
||||
string(SQLPackagePGXV5): {},
|
||||
string(SQLPackageStandard): {},
|
||||
}
|
||||
|
||||
func validatePackage(sqlPackage string) error {
|
||||
if _, found := validPackages[sqlPackage]; !found {
|
||||
return fmt.Errorf("unknown SQL package: %s", sqlPackage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
SQLDriverPGXV4 SQLDriver = "github.com/jackc/pgx/v4"
|
||||
SQLDriverPGXV5 = "github.com/jackc/pgx/v5"
|
||||
SQLDriverLibPQ = "github.com/lib/pq"
|
||||
SQLDriverGoSQLDriverMySQL = "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
var validDrivers = map[string]struct{}{
|
||||
string(SQLDriverPGXV4): {},
|
||||
string(SQLDriverPGXV5): {},
|
||||
string(SQLDriverLibPQ): {},
|
||||
string(SQLDriverGoSQLDriverMySQL): {},
|
||||
}
|
||||
|
||||
func validateDriver(sqlDriver string) error {
|
||||
if _, found := validDrivers[sqlDriver]; !found {
|
||||
return fmt.Errorf("unknown SQL driver: %s", sqlDriver)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d SQLDriver) IsPGX() bool {
|
||||
return d == SQLDriverPGXV4 || d == SQLDriverPGXV5
|
||||
}
|
||||
|
||||
func (d SQLDriver) IsGoSQLDriverMySQL() bool {
|
||||
return d == SQLDriverGoSQLDriverMySQL
|
||||
}
|
||||
|
||||
func (d SQLDriver) Package() string {
|
||||
switch d {
|
||||
case SQLDriverPGXV4:
|
||||
return SQLPackagePGXV4
|
||||
case SQLDriverPGXV5:
|
||||
return SQLPackagePGXV5
|
||||
default:
|
||||
return SQLPackageStandard
|
||||
}
|
||||
}
|
||||
192
internal/codegen/golang/opts/go_type.go
Normal file
192
internal/codegen/golang/opts/go_type.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/structtag"
|
||||
)
|
||||
|
||||
type GoType struct {
|
||||
Path string `json:"import" yaml:"import"`
|
||||
Package string `json:"package" yaml:"package"`
|
||||
Name string `json:"type" yaml:"type"`
|
||||
Pointer bool `json:"pointer" yaml:"pointer"`
|
||||
Slice bool `json:"slice" yaml:"slice"`
|
||||
Spec string `json:"-"`
|
||||
BuiltIn bool `json:"-"`
|
||||
}
|
||||
|
||||
type ParsedGoType struct {
|
||||
ImportPath string
|
||||
Package string
|
||||
TypeName string
|
||||
BasicType bool
|
||||
StructTag string
|
||||
}
|
||||
|
||||
func (o *GoType) MarshalJSON() ([]byte, error) {
|
||||
if o.Spec != "" {
|
||||
return json.Marshal(o.Spec)
|
||||
}
|
||||
type alias GoType
|
||||
return json.Marshal(alias(*o))
|
||||
}
|
||||
|
||||
func (o *GoType) UnmarshalJSON(data []byte) error {
|
||||
var spec string
|
||||
if err := json.Unmarshal(data, &spec); err == nil {
|
||||
*o = GoType{Spec: spec}
|
||||
return nil
|
||||
}
|
||||
type alias GoType
|
||||
var a alias
|
||||
if err := json.Unmarshal(data, &a); err != nil {
|
||||
return err
|
||||
}
|
||||
*o = GoType(a)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *GoType) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var spec string
|
||||
if err := unmarshal(&spec); err == nil {
|
||||
*o = GoType{Spec: spec}
|
||||
return nil
|
||||
}
|
||||
type alias GoType
|
||||
var a alias
|
||||
if err := unmarshal(&a); err != nil {
|
||||
return err
|
||||
}
|
||||
*o = GoType(a)
|
||||
return nil
|
||||
}
|
||||
|
||||
var validIdentifier = regexp.MustCompile(`^[a-zA-Z0-9_]+$`)
|
||||
var versionNumber = regexp.MustCompile(`^v[0-9]+$`)
|
||||
var invalidIdentifier = regexp.MustCompile(`[^a-zA-Z0-9_]`)
|
||||
|
||||
func generatePackageID(importPath string) (string, bool) {
|
||||
parts := strings.Split(importPath, "/")
|
||||
name := parts[len(parts)-1]
|
||||
// If the last part of the import path is a valid identifier, assume that's the package name
|
||||
if versionNumber.MatchString(name) && len(parts) >= 2 {
|
||||
name = parts[len(parts)-2]
|
||||
return invalidIdentifier.ReplaceAllString(strings.ToLower(name), "_"), true
|
||||
}
|
||||
if validIdentifier.MatchString(name) {
|
||||
return name, false
|
||||
}
|
||||
return invalidIdentifier.ReplaceAllString(strings.ToLower(name), "_"), true
|
||||
}
|
||||
|
||||
// validate GoType
|
||||
func (gt GoType) parse() (*ParsedGoType, error) {
|
||||
var o ParsedGoType
|
||||
|
||||
if gt.Spec == "" {
|
||||
// TODO: Validation
|
||||
if gt.Path == "" && gt.Package != "" {
|
||||
return nil, fmt.Errorf("Package override `go_type`: package name requires an import path")
|
||||
}
|
||||
var pkg string
|
||||
var pkgNeedsAlias bool
|
||||
|
||||
if gt.Package == "" && gt.Path != "" {
|
||||
pkg, pkgNeedsAlias = generatePackageID(gt.Path)
|
||||
if pkgNeedsAlias {
|
||||
o.Package = pkg
|
||||
}
|
||||
} else {
|
||||
pkg = gt.Package
|
||||
o.Package = gt.Package
|
||||
}
|
||||
|
||||
o.ImportPath = gt.Path
|
||||
o.TypeName = gt.Name
|
||||
o.BasicType = gt.Path == "" && gt.Package == ""
|
||||
if pkg != "" {
|
||||
o.TypeName = pkg + "." + o.TypeName
|
||||
}
|
||||
if gt.Pointer {
|
||||
o.TypeName = "*" + o.TypeName
|
||||
}
|
||||
if gt.Slice {
|
||||
o.TypeName = "[]" + o.TypeName
|
||||
}
|
||||
return &o, nil
|
||||
}
|
||||
|
||||
input := gt.Spec
|
||||
lastDot := strings.LastIndex(input, ".")
|
||||
lastSlash := strings.LastIndex(input, "/")
|
||||
typename := input
|
||||
if lastDot == -1 && lastSlash == -1 {
|
||||
// if the type name has no slash and no dot, validate that the type is a basic Go type
|
||||
var found bool
|
||||
for _, typ := range types.Typ {
|
||||
info := typ.Info()
|
||||
if info == 0 {
|
||||
continue
|
||||
}
|
||||
if info&types.IsUntyped != 0 {
|
||||
continue
|
||||
}
|
||||
if typename == typ.Name() {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("Package override `go_type` specifier %q is not a Go basic type e.g. 'string'", input)
|
||||
}
|
||||
o.BasicType = true
|
||||
} else {
|
||||
// assume the type lives in a Go package
|
||||
if lastDot == -1 {
|
||||
return nil, fmt.Errorf("Package override `go_type` specifier %q is not the proper format, expected 'package.type', e.g. 'github.com/segmentio/ksuid.KSUID'", input)
|
||||
}
|
||||
typename = input[lastSlash+1:]
|
||||
// a package name beginning with "go-" will give syntax errors in
|
||||
// generated code. We should do the right thing and get the actual
|
||||
// import name, but in lieu of that, stripping the leading "go-" may get
|
||||
// us what we want.
|
||||
typename = strings.TrimPrefix(typename, "go-")
|
||||
typename = strings.TrimSuffix(typename, "-go")
|
||||
o.ImportPath = input[:lastDot]
|
||||
}
|
||||
o.TypeName = typename
|
||||
isPointer := input[0] == '*'
|
||||
if isPointer {
|
||||
o.ImportPath = o.ImportPath[1:]
|
||||
o.TypeName = "*" + o.TypeName
|
||||
}
|
||||
return &o, nil
|
||||
}
|
||||
|
||||
// GoStructTag is a raw Go struct tag.
|
||||
type GoStructTag string
|
||||
|
||||
// Parse parses and validates a GoStructTag.
|
||||
// The output is in a form convenient for codegen.
|
||||
//
|
||||
// Sample valid inputs/outputs:
|
||||
//
|
||||
// In Out
|
||||
// empty string {}
|
||||
// `a:"b"` {"a": "b"}
|
||||
// `a:"b" x:"y,z"` {"a": "b", "x": "y,z"}
|
||||
func (s GoStructTag) parse() (map[string]string, error) {
|
||||
m := make(map[string]string)
|
||||
tags, err := structtag.Parse(string(s))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, tag := range tags.Tags() {
|
||||
m[tag.Key] = tag.Value()
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
243
internal/codegen/golang/opts/options.go
Normal file
243
internal/codegen/golang/opts/options.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
type Options struct {
|
||||
EmitInterface bool `json:"emit_interface" yaml:"emit_interface"`
|
||||
EmitJsonTags bool `json:"emit_json_tags" yaml:"emit_json_tags"`
|
||||
JsonTagsIdUppercase bool `json:"json_tags_id_uppercase" yaml:"json_tags_id_uppercase"`
|
||||
EmitDbTags bool `json:"emit_db_tags" yaml:"emit_db_tags"`
|
||||
EmitPreparedQueries bool `json:"emit_prepared_queries" yaml:"emit_prepared_queries"`
|
||||
EmitExactTableNames bool `json:"emit_exact_table_names,omitempty" yaml:"emit_exact_table_names"`
|
||||
EmitEmptySlices bool `json:"emit_empty_slices,omitempty" yaml:"emit_empty_slices"`
|
||||
EmitExportedQueries bool `json:"emit_exported_queries" yaml:"emit_exported_queries"`
|
||||
EmitResultStructPointers bool `json:"emit_result_struct_pointers" yaml:"emit_result_struct_pointers"`
|
||||
EmitParamsStructPointers bool `json:"emit_params_struct_pointers" yaml:"emit_params_struct_pointers"`
|
||||
EmitMethodsWithDbArgument bool `json:"emit_methods_with_db_argument,omitempty" yaml:"emit_methods_with_db_argument"`
|
||||
EmitPointersForNullTypes bool `json:"emit_pointers_for_null_types" yaml:"emit_pointers_for_null_types"`
|
||||
// nil inherits EmitPointersForNullTypes; non-nil overrides for enums only.
|
||||
EmitPointersForNullEnumTypes *bool `json:"emit_pointers_for_null_enum_types,omitempty" yaml:"emit_pointers_for_null_enum_types"`
|
||||
EmitEnumValidMethod bool `json:"emit_enum_valid_method,omitempty" yaml:"emit_enum_valid_method"`
|
||||
EmitAllEnumValues bool `json:"emit_all_enum_values,omitempty" yaml:"emit_all_enum_values"`
|
||||
EmitSqlAsComment bool `json:"emit_sql_as_comment,omitempty" yaml:"emit_sql_as_comment"`
|
||||
JsonTagsCaseStyle string `json:"json_tags_case_style,omitempty" yaml:"json_tags_case_style"`
|
||||
Package string `json:"package" yaml:"package"`
|
||||
Out string `json:"out" yaml:"out"`
|
||||
Overrides []Override `json:"overrides,omitempty" yaml:"overrides"`
|
||||
Rename map[string]string `json:"rename,omitempty" yaml:"rename"`
|
||||
SqlPackage string `json:"sql_package" yaml:"sql_package"`
|
||||
SqlDriver string `json:"sql_driver" yaml:"sql_driver"`
|
||||
OutputBatchFileName string `json:"output_batch_file_name,omitempty" yaml:"output_batch_file_name"`
|
||||
OutputDbFileName string `json:"output_db_file_name,omitempty" yaml:"output_db_file_name"`
|
||||
OutputModelsFileName string `json:"output_models_file_name,omitempty" yaml:"output_models_file_name"`
|
||||
OutputModelsPath string `json:"output_models_path,omitempty" yaml:"output_models_path"`
|
||||
OutputModelsPackage string `json:"output_models_package,omitempty" yaml:"output_models_package"`
|
||||
OutputModelsImport string `json:"output_models_import,omitempty" yaml:"output_models_import"`
|
||||
OutputModelsEmit *bool `json:"output_models_emit,omitempty" yaml:"output_models_emit"`
|
||||
OutputQuerierFileName string `json:"output_querier_file_name,omitempty" yaml:"output_querier_file_name"`
|
||||
OutputCopyfromFileName string `json:"output_copyfrom_file_name,omitempty" yaml:"output_copyfrom_file_name"`
|
||||
OutputFilesSuffix string `json:"output_files_suffix,omitempty" yaml:"output_files_suffix"`
|
||||
InflectionExcludeTableNames []string `json:"inflection_exclude_table_names,omitempty" yaml:"inflection_exclude_table_names"`
|
||||
WrapErrors bool `json:"wrap_errors,omitempty" yaml:"wrap_errors"`
|
||||
QueryParameterLimit *int32 `json:"query_parameter_limit,omitempty" yaml:"query_parameter_limit"`
|
||||
OmitSqlcVersion bool `json:"omit_sqlc_version,omitempty" yaml:"omit_sqlc_version"`
|
||||
OmitUnusedStructs bool `json:"omit_unused_structs,omitempty" yaml:"omit_unused_structs"`
|
||||
BuildTags string `json:"build_tags,omitempty" yaml:"build_tags"`
|
||||
Initialisms *[]string `json:"initialisms,omitempty" yaml:"initialisms"`
|
||||
|
||||
InitialismsMap map[string]struct{} `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
type GlobalOptions struct {
|
||||
Overrides []Override `json:"overrides,omitempty" yaml:"overrides"`
|
||||
Rename map[string]string `json:"rename,omitempty" yaml:"rename"`
|
||||
}
|
||||
|
||||
func Parse(req *plugin.GenerateRequest) (*Options, error) {
|
||||
options, err := parseOpts(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
global, err := parseGlobalOpts(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(global.Overrides) > 0 {
|
||||
options.Overrides = append(global.Overrides, options.Overrides...)
|
||||
}
|
||||
if len(global.Rename) > 0 {
|
||||
if options.Rename == nil {
|
||||
options.Rename = map[string]string{}
|
||||
}
|
||||
maps.Copy(options.Rename, global.Rename)
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func parseOpts(req *plugin.GenerateRequest) (*Options, error) {
|
||||
var options Options
|
||||
if len(req.PluginOptions) == 0 {
|
||||
return &options, nil
|
||||
}
|
||||
if err := json.Unmarshal(req.PluginOptions, &options); err != nil {
|
||||
return nil, fmt.Errorf("unmarshalling plugin options: %w", err)
|
||||
}
|
||||
|
||||
if options.Package == "" {
|
||||
if options.Out != "" {
|
||||
options.Package = filepath.Base(options.Out)
|
||||
} else {
|
||||
return nil, fmt.Errorf("invalid options: missing package name")
|
||||
}
|
||||
}
|
||||
|
||||
// Default the models package name to the base of the models path. When
|
||||
// the user only configures output_models_emit: false (no path), fall
|
||||
// back to the base of the import path.
|
||||
if options.OutputModelsPackage == "" {
|
||||
if options.OutputModelsPath != "" {
|
||||
options.OutputModelsPackage = filepath.Base(options.OutputModelsPath)
|
||||
} else if options.OutputModelsImport != "" {
|
||||
options.OutputModelsPackage = filepath.Base(options.OutputModelsImport)
|
||||
}
|
||||
}
|
||||
|
||||
for i := range options.Overrides {
|
||||
if err := options.Overrides[i].parse(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if options.SqlPackage != "" {
|
||||
if err := validatePackage(options.SqlPackage); err != nil {
|
||||
return nil, fmt.Errorf("invalid options: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if options.SqlDriver != "" {
|
||||
if err := validateDriver(options.SqlDriver); err != nil {
|
||||
return nil, fmt.Errorf("invalid options: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if options.QueryParameterLimit == nil {
|
||||
options.QueryParameterLimit = new(int32)
|
||||
*options.QueryParameterLimit = 1
|
||||
}
|
||||
|
||||
if options.Initialisms == nil {
|
||||
options.Initialisms = new([]string)
|
||||
*options.Initialisms = []string{"id"}
|
||||
}
|
||||
|
||||
options.InitialismsMap = map[string]struct{}{}
|
||||
for _, initial := range *options.Initialisms {
|
||||
options.InitialismsMap[initial] = struct{}{}
|
||||
}
|
||||
|
||||
return &options, nil
|
||||
}
|
||||
|
||||
func parseGlobalOpts(req *plugin.GenerateRequest) (*GlobalOptions, error) {
|
||||
var options GlobalOptions
|
||||
if len(req.GlobalOptions) == 0 {
|
||||
return &options, nil
|
||||
}
|
||||
if err := json.Unmarshal(req.GlobalOptions, &options); err != nil {
|
||||
return nil, fmt.Errorf("unmarshalling global options: %w", err)
|
||||
}
|
||||
for i := range options.Overrides {
|
||||
if err := options.Overrides[i].parse(req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &options, nil
|
||||
}
|
||||
|
||||
func ValidateOpts(opts *Options) error {
|
||||
if opts.EmitMethodsWithDbArgument && opts.EmitPreparedQueries {
|
||||
return fmt.Errorf("invalid options: emit_methods_with_db_argument and emit_prepared_queries options are mutually exclusive")
|
||||
}
|
||||
if *opts.QueryParameterLimit < 0 {
|
||||
return fmt.Errorf("invalid options: query parameter limit must not be negative")
|
||||
}
|
||||
|
||||
if err := validateModelsOptions(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ModelsEmitEnabled reports whether this codegen block should write the
|
||||
// models file. Defaults to true when the option is unset.
|
||||
func (o *Options) ModelsEmitEnabled() bool {
|
||||
if o.OutputModelsEmit == nil {
|
||||
return true
|
||||
}
|
||||
return *o.OutputModelsEmit
|
||||
}
|
||||
|
||||
// ModelsImportAlias is the fixed Go import alias used for the models
|
||||
// package in query files. Using a constant alias keeps the type qualifier
|
||||
// consistent regardless of how the user names the actual package.
|
||||
const ModelsImportAlias = "models"
|
||||
|
||||
// ModelsPackage returns the Go package name to use in the models file
|
||||
// itself (i.e. the `package X` declaration). When the caller has not
|
||||
// configured a separate models package, this is the same as Package.
|
||||
func (o *Options) ModelsPackage() string {
|
||||
if o.OutputModelsPackage != "" {
|
||||
return o.OutputModelsPackage
|
||||
}
|
||||
return o.Package
|
||||
}
|
||||
|
||||
// ModelsAreExternal reports whether model types live in a different Go
|
||||
// package than the queries package. When true, query files must import the
|
||||
// models package and reference types as `models.Type`.
|
||||
func (o *Options) ModelsAreExternal() bool {
|
||||
return o.OutputModelsImport != ""
|
||||
}
|
||||
|
||||
// ModelsTypeQualifier returns the prefix to use when referencing a model
|
||||
// type from a query file ("models."). Empty string when no qualifier is
|
||||
// needed.
|
||||
func (o *Options) ModelsTypeQualifier() string {
|
||||
if o.ModelsAreExternal() {
|
||||
return ModelsImportAlias + "."
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func validateModelsOptions(opts *Options) error {
|
||||
hasAnyModelsOpt := opts.OutputModelsPath != "" ||
|
||||
opts.OutputModelsPackage != "" ||
|
||||
opts.OutputModelsImport != "" ||
|
||||
opts.OutputModelsEmit != nil
|
||||
|
||||
if !hasAnyModelsOpt {
|
||||
return nil
|
||||
}
|
||||
|
||||
if opts.OutputModelsImport == "" {
|
||||
return fmt.Errorf("invalid options: output_models_import is required when any output_models_* option is set")
|
||||
}
|
||||
|
||||
if opts.ModelsEmitEnabled() && opts.OutputModelsPath == "" {
|
||||
return fmt.Errorf("invalid options: output_models_path is required when emitting models to a separate package")
|
||||
}
|
||||
|
||||
if opts.ModelsEmitEnabled() && opts.OutputModelsPath == opts.Out {
|
||||
return fmt.Errorf("invalid options: output_models_path matches out; models would overwrite the queries package")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
176
internal/codegen/golang/opts/override.go
Normal file
176
internal/codegen/golang/opts/override.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/sdk"
|
||||
"github.com/sqlc-dev/sqlc/internal/pattern"
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
type Override struct {
|
||||
// name of the golang type to use, e.g. `github.com/segmentio/ksuid.KSUID`
|
||||
GoType GoType `json:"go_type" yaml:"go_type"`
|
||||
|
||||
// additional Go struct tags to add to this field, in raw Go struct tag form, e.g. `validate:"required" x:"y,z"`
|
||||
// see https://github.com/sqlc-dev/sqlc/issues/534
|
||||
GoStructTag GoStructTag `json:"go_struct_tag" yaml:"go_struct_tag"`
|
||||
|
||||
// fully qualified name of the Go type, e.g. `github.com/segmentio/ksuid.KSUID`
|
||||
DBType string `json:"db_type" yaml:"db_type"`
|
||||
Deprecated_PostgresType string `json:"postgres_type" yaml:"postgres_type"`
|
||||
|
||||
// for global overrides only when two different engines are in use
|
||||
Engine string `json:"engine,omitempty" yaml:"engine"`
|
||||
|
||||
// True if the GoType should override if the matching type is nullable
|
||||
Nullable bool `json:"nullable" yaml:"nullable"`
|
||||
|
||||
// True if the GoType should override if the matching type is unsiged.
|
||||
Unsigned bool `json:"unsigned" yaml:"unsigned"`
|
||||
|
||||
// Deprecated. Use the `nullable` property instead
|
||||
Deprecated_Null bool `json:"null" yaml:"null"`
|
||||
|
||||
// fully qualified name of the column, e.g. `accounts.id`
|
||||
Column string `json:"column" yaml:"column"`
|
||||
|
||||
ColumnName *pattern.Match `json:"-"`
|
||||
TableCatalog *pattern.Match `json:"-"`
|
||||
TableSchema *pattern.Match `json:"-"`
|
||||
TableRel *pattern.Match `json:"-"`
|
||||
GoImportPath string `json:"-"`
|
||||
GoPackage string `json:"-"`
|
||||
GoTypeName string `json:"-"`
|
||||
GoBasicType bool `json:"-"`
|
||||
|
||||
// Parsed form of GoStructTag, e.g. {"validate:", "required"}
|
||||
GoStructTags map[string]string `json:"-"`
|
||||
ShimOverride *ShimOverride `json:"-"`
|
||||
}
|
||||
|
||||
func (o *Override) Matches(n *plugin.Identifier, defaultSchema string) bool {
|
||||
if n == nil {
|
||||
return false
|
||||
}
|
||||
schema := n.Schema
|
||||
if n.Schema == "" {
|
||||
schema = defaultSchema
|
||||
}
|
||||
if o.TableCatalog != nil && !o.TableCatalog.MatchString(n.Catalog) {
|
||||
return false
|
||||
}
|
||||
if o.TableSchema == nil && schema != "" {
|
||||
return false
|
||||
}
|
||||
if o.TableSchema != nil && !o.TableSchema.MatchString(schema) {
|
||||
return false
|
||||
}
|
||||
if o.TableRel == nil && n.Name != "" {
|
||||
return false
|
||||
}
|
||||
if o.TableRel != nil && !o.TableRel.MatchString(n.Name) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *Override) MatchesColumn(col *plugin.Column) bool {
|
||||
columnType := sdk.DataType(col.Type)
|
||||
notNull := col.NotNull || col.IsArray
|
||||
return o.DBType != "" && o.DBType == columnType && o.Nullable != notNull && o.Unsigned == col.Unsigned
|
||||
}
|
||||
|
||||
func (o *Override) parse(req *plugin.GenerateRequest) (err error) {
|
||||
// validate deprecated postgres_type field
|
||||
if o.Deprecated_PostgresType != "" {
|
||||
fmt.Fprintf(os.Stderr, "WARNING: \"postgres_type\" is deprecated. Instead, use \"db_type\" to specify a type override.\n")
|
||||
if o.DBType != "" {
|
||||
return fmt.Errorf(`Type override configurations cannot have "db_type" and "postres_type" together. Use "db_type" alone`)
|
||||
}
|
||||
o.DBType = o.Deprecated_PostgresType
|
||||
}
|
||||
|
||||
// validate deprecated null field
|
||||
if o.Deprecated_Null {
|
||||
fmt.Fprintf(os.Stderr, "WARNING: \"null\" is deprecated. Instead, use the \"nullable\" field.\n")
|
||||
o.Nullable = true
|
||||
}
|
||||
|
||||
schema := "public"
|
||||
if req != nil && req.Catalog != nil {
|
||||
schema = req.Catalog.DefaultSchema
|
||||
}
|
||||
|
||||
// validate option combinations
|
||||
switch {
|
||||
case o.Column != "" && o.DBType != "":
|
||||
return fmt.Errorf("Override specifying both `column` (%q) and `db_type` (%q) is not valid.", o.Column, o.DBType)
|
||||
case o.Column == "" && o.DBType == "":
|
||||
return fmt.Errorf("Override must specify one of either `column` or `db_type`")
|
||||
}
|
||||
|
||||
// validate Column
|
||||
if o.Column != "" {
|
||||
colParts := strings.Split(o.Column, ".")
|
||||
switch len(colParts) {
|
||||
case 2:
|
||||
if o.ColumnName, err = pattern.MatchCompile(colParts[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.TableRel, err = pattern.MatchCompile(colParts[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.TableSchema, err = pattern.MatchCompile(schema); err != nil {
|
||||
return err
|
||||
}
|
||||
case 3:
|
||||
if o.ColumnName, err = pattern.MatchCompile(colParts[2]); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.TableRel, err = pattern.MatchCompile(colParts[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.TableSchema, err = pattern.MatchCompile(colParts[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
case 4:
|
||||
if o.ColumnName, err = pattern.MatchCompile(colParts[3]); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.TableRel, err = pattern.MatchCompile(colParts[2]); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.TableSchema, err = pattern.MatchCompile(colParts[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.TableCatalog, err = pattern.MatchCompile(colParts[0]); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("Override `column` specifier %q is not the proper format, expected '[catalog.][schema.]tablename.colname'", o.Column)
|
||||
}
|
||||
}
|
||||
|
||||
// validate GoType
|
||||
parsed, err := o.GoType.parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.GoImportPath = parsed.ImportPath
|
||||
o.GoPackage = parsed.Package
|
||||
o.GoTypeName = parsed.TypeName
|
||||
o.GoBasicType = parsed.BasicType
|
||||
|
||||
// validate GoStructTag
|
||||
tags, err := o.GoStructTag.parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.GoStructTags = tags
|
||||
|
||||
o.ShimOverride = shimOverride(req, o)
|
||||
return nil
|
||||
}
|
||||
117
internal/codegen/golang/opts/override_test.go
Normal file
117
internal/codegen/golang/opts/override_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func TestTypeOverrides(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
override Override
|
||||
pkg string
|
||||
typeName string
|
||||
basic bool
|
||||
}{
|
||||
{
|
||||
Override{
|
||||
DBType: "uuid",
|
||||
GoType: GoType{Spec: "github.com/segmentio/ksuid.KSUID"},
|
||||
},
|
||||
"github.com/segmentio/ksuid",
|
||||
"ksuid.KSUID",
|
||||
false,
|
||||
},
|
||||
// TODO: Add test for struct pointers
|
||||
//
|
||||
// {
|
||||
// Override{
|
||||
// DBType: "uuid",
|
||||
// GoType: "github.com/segmentio/*ksuid.KSUID",
|
||||
// },
|
||||
// "github.com/segmentio/ksuid",
|
||||
// "*ksuid.KSUID",
|
||||
// false,
|
||||
// },
|
||||
{
|
||||
Override{
|
||||
DBType: "citext",
|
||||
GoType: GoType{Spec: "string"},
|
||||
},
|
||||
"",
|
||||
"string",
|
||||
true,
|
||||
},
|
||||
{
|
||||
Override{
|
||||
DBType: "timestamp",
|
||||
GoType: GoType{Spec: "time.Time"},
|
||||
},
|
||||
"time",
|
||||
"time.Time",
|
||||
false,
|
||||
},
|
||||
} {
|
||||
tt := test
|
||||
t.Run(tt.override.GoType.Spec, func(t *testing.T) {
|
||||
if err := tt.override.parse(nil); err != nil {
|
||||
t.Fatalf("override parsing failed; %s", err)
|
||||
}
|
||||
if diff := cmp.Diff(tt.pkg, tt.override.GoImportPath); diff != "" {
|
||||
t.Errorf("package mismatch;\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tt.typeName, tt.override.GoTypeName); diff != "" {
|
||||
t.Errorf("type name mismatch;\n%s", diff)
|
||||
}
|
||||
if diff := cmp.Diff(tt.basic, tt.override.GoBasicType); diff != "" {
|
||||
t.Errorf("basic mismatch;\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
for _, test := range []struct {
|
||||
override Override
|
||||
err string
|
||||
}{
|
||||
{
|
||||
Override{
|
||||
DBType: "uuid",
|
||||
GoType: GoType{Spec: "Pointer"},
|
||||
},
|
||||
"Package override `go_type` specifier \"Pointer\" is not a Go basic type e.g. 'string'",
|
||||
},
|
||||
{
|
||||
Override{
|
||||
DBType: "uuid",
|
||||
GoType: GoType{Spec: "untyped rune"},
|
||||
},
|
||||
"Package override `go_type` specifier \"untyped rune\" is not a Go basic type e.g. 'string'",
|
||||
},
|
||||
} {
|
||||
tt := test
|
||||
t.Run(tt.override.GoType.Spec, func(t *testing.T) {
|
||||
err := tt.override.parse(nil)
|
||||
if err == nil {
|
||||
t.Fatalf("expected parse to fail; got nil")
|
||||
}
|
||||
if diff := cmp.Diff(tt.err, err.Error()); diff != "" {
|
||||
t.Errorf("error mismatch;\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func FuzzOverride(f *testing.F) {
|
||||
for _, spec := range []string{
|
||||
"string",
|
||||
"github.com/gofrs/uuid.UUID",
|
||||
"github.com/segmentio/ksuid.KSUID",
|
||||
} {
|
||||
f.Add(spec)
|
||||
}
|
||||
f.Fuzz(func(t *testing.T, s string) {
|
||||
o := Override{
|
||||
GoType: GoType{Spec: s},
|
||||
}
|
||||
o.parse(nil)
|
||||
})
|
||||
}
|
||||
77
internal/codegen/golang/opts/shim.go
Normal file
77
internal/codegen/golang/opts/shim.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package opts
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
// The ShimOverride struct exists to bridge the gap between the Override struct
|
||||
// and the previous Override struct defined in codegen.proto. Eventually these
|
||||
// shim structs should be removed in favor of using the existing Override and
|
||||
// GoType structs, but it's easier to provide these shim structs to not change
|
||||
// the existing, working code.
|
||||
type ShimOverride struct {
|
||||
DbType string
|
||||
Nullable bool
|
||||
Column string
|
||||
Table *plugin.Identifier
|
||||
ColumnName string
|
||||
Unsigned bool
|
||||
GoType *ShimGoType
|
||||
}
|
||||
|
||||
func shimOverride(req *plugin.GenerateRequest, o *Override) *ShimOverride {
|
||||
var column string
|
||||
var table plugin.Identifier
|
||||
|
||||
if o.Column != "" {
|
||||
colParts := strings.Split(o.Column, ".")
|
||||
switch len(colParts) {
|
||||
case 2:
|
||||
table.Schema = req.Catalog.DefaultSchema
|
||||
table.Name = colParts[0]
|
||||
column = colParts[1]
|
||||
case 3:
|
||||
table.Schema = colParts[0]
|
||||
table.Name = colParts[1]
|
||||
column = colParts[2]
|
||||
case 4:
|
||||
table.Catalog = colParts[0]
|
||||
table.Schema = colParts[1]
|
||||
table.Name = colParts[2]
|
||||
column = colParts[3]
|
||||
}
|
||||
}
|
||||
return &ShimOverride{
|
||||
DbType: o.DBType,
|
||||
Nullable: o.Nullable,
|
||||
Unsigned: o.Unsigned,
|
||||
Column: o.Column,
|
||||
ColumnName: column,
|
||||
Table: &table,
|
||||
GoType: shimGoType(o),
|
||||
}
|
||||
}
|
||||
|
||||
type ShimGoType struct {
|
||||
ImportPath string
|
||||
Package string
|
||||
TypeName string
|
||||
BasicType bool
|
||||
StructTags map[string]string
|
||||
}
|
||||
|
||||
func shimGoType(o *Override) *ShimGoType {
|
||||
// Note that there is a slight mismatch between this and the
|
||||
// proto api. The GoType on the override is the unparsed type,
|
||||
// which could be a qualified path or an object, as per
|
||||
// https://docs.sqlc.dev/en/v1.18.0/reference/config.html#type-overriding
|
||||
return &ShimGoType{
|
||||
ImportPath: o.GoImportPath,
|
||||
Package: o.GoPackage,
|
||||
TypeName: o.GoTypeName,
|
||||
BasicType: o.GoBasicType,
|
||||
StructTags: o.GoStructTags,
|
||||
}
|
||||
}
|
||||
619
internal/codegen/golang/postgresql_type.go
Normal file
619
internal/codegen/golang/postgresql_type.go
Normal file
@@ -0,0 +1,619 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/sdk"
|
||||
"github.com/sqlc-dev/sqlc/internal/debug"
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
func parseIdentifierString(name string) (*plugin.Identifier, error) {
|
||||
parts := strings.Split(name, ".")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
return &plugin.Identifier{
|
||||
Name: parts[0],
|
||||
}, nil
|
||||
case 2:
|
||||
return &plugin.Identifier{
|
||||
Schema: parts[0],
|
||||
Name: parts[1],
|
||||
}, nil
|
||||
case 3:
|
||||
return &plugin.Identifier{
|
||||
Catalog: parts[0],
|
||||
Schema: parts[1],
|
||||
Name: parts[2],
|
||||
}, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid name: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
func postgresType(req *plugin.GenerateRequest, options *opts.Options, col *plugin.Column) string {
|
||||
columnType := sdk.DataType(col.Type)
|
||||
notNull := col.NotNull || col.IsArray
|
||||
driver := parseDriver(options.SqlPackage)
|
||||
emitPointersForNull := driver.IsPGX() && options.EmitPointersForNullTypes
|
||||
emitPointersForNullEnums := emitPointersForNull
|
||||
if options.EmitPointersForNullEnumTypes != nil {
|
||||
emitPointersForNullEnums = driver.IsPGX() && *options.EmitPointersForNullEnumTypes
|
||||
}
|
||||
|
||||
switch columnType {
|
||||
case "serial", "serial4", "pg_catalog.serial4":
|
||||
if notNull {
|
||||
return "int32"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*int32"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Int4"
|
||||
}
|
||||
return "sql.NullInt32"
|
||||
|
||||
case "bigserial", "serial8", "pg_catalog.serial8":
|
||||
if notNull {
|
||||
return "int64"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*int64"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Int8"
|
||||
}
|
||||
return "sql.NullInt64"
|
||||
|
||||
case "smallserial", "serial2", "pg_catalog.serial2":
|
||||
if notNull {
|
||||
return "int16"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*int16"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Int2"
|
||||
}
|
||||
return "sql.NullInt16"
|
||||
|
||||
case "integer", "int", "int4", "pg_catalog.int4":
|
||||
if notNull {
|
||||
return "int32"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*int32"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Int4"
|
||||
}
|
||||
return "sql.NullInt32"
|
||||
|
||||
case "bigint", "int8", "pg_catalog.int8":
|
||||
if notNull {
|
||||
return "int64"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*int64"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Int8"
|
||||
}
|
||||
return "sql.NullInt64"
|
||||
|
||||
case "smallint", "int2", "pg_catalog.int2":
|
||||
if notNull {
|
||||
return "int16"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*int16"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Int2"
|
||||
}
|
||||
return "sql.NullInt16"
|
||||
|
||||
case "float", "double precision", "float8", "pg_catalog.float8":
|
||||
if notNull {
|
||||
return "float64"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*float64"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Float8"
|
||||
}
|
||||
return "sql.NullFloat64"
|
||||
|
||||
case "real", "float4", "pg_catalog.float4":
|
||||
if notNull {
|
||||
return "float32"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*float32"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Float4"
|
||||
}
|
||||
return "sql.NullFloat64" // TODO: Change to sql.NullFloat32 after updating the go.mod file
|
||||
|
||||
case "numeric", "pg_catalog.numeric", "money":
|
||||
if driver.IsPGX() {
|
||||
return "pgtype.Numeric"
|
||||
}
|
||||
// Since the Go standard library does not have a decimal type, lib/pq
|
||||
// returns numerics as strings.
|
||||
//
|
||||
// https://github.com/lib/pq/issues/648
|
||||
if notNull {
|
||||
return "string"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*string"
|
||||
}
|
||||
return "sql.NullString"
|
||||
|
||||
case "boolean", "bool", "pg_catalog.bool":
|
||||
if notNull {
|
||||
return "bool"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*bool"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Bool"
|
||||
}
|
||||
return "sql.NullBool"
|
||||
|
||||
case "json", "pg_catalog.json":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "[]byte"
|
||||
case opts.SQLDriverPGXV4:
|
||||
return "pgtype.JSON"
|
||||
case opts.SQLDriverLibPQ:
|
||||
if notNull {
|
||||
return "json.RawMessage"
|
||||
} else {
|
||||
return "pqtype.NullRawMessage"
|
||||
}
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "jsonb", "pg_catalog.jsonb":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "[]byte"
|
||||
case opts.SQLDriverPGXV4:
|
||||
return "pgtype.JSONB"
|
||||
case opts.SQLDriverLibPQ:
|
||||
if notNull {
|
||||
return "json.RawMessage"
|
||||
} else {
|
||||
return "pqtype.NullRawMessage"
|
||||
}
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "bytea", "blob", "pg_catalog.bytea":
|
||||
return "[]byte"
|
||||
|
||||
case "date":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Date"
|
||||
}
|
||||
if notNull {
|
||||
return "time.Time"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*time.Time"
|
||||
}
|
||||
return "sql.NullTime"
|
||||
|
||||
case "pg_catalog.time", "time", "time without time zone":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Time"
|
||||
}
|
||||
if notNull {
|
||||
return "time.Time"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*time.Time"
|
||||
}
|
||||
return "sql.NullTime"
|
||||
|
||||
case "pg_catalog.timetz", "timetz", "time with time zone":
|
||||
if notNull {
|
||||
return "time.Time"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*time.Time"
|
||||
}
|
||||
return "sql.NullTime"
|
||||
|
||||
case "pg_catalog.timestamp", "timestamp", "timestamp without time zone":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Timestamp"
|
||||
}
|
||||
if notNull {
|
||||
return "time.Time"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*time.Time"
|
||||
}
|
||||
return "sql.NullTime"
|
||||
|
||||
case "pg_catalog.timestamptz", "timestamptz", "timestamp with time zone":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Timestamptz"
|
||||
}
|
||||
if notNull {
|
||||
return "time.Time"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*time.Time"
|
||||
}
|
||||
return "sql.NullTime"
|
||||
|
||||
case "text", "pg_catalog.varchar", "varchar", "character varying", "pg_catalog.bpchar", "bpchar", "character", "string", "citext", "name":
|
||||
if notNull {
|
||||
return "string"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*string"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Text"
|
||||
}
|
||||
return "sql.NullString"
|
||||
|
||||
case "uuid":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.UUID"
|
||||
}
|
||||
if notNull {
|
||||
return "uuid.UUID"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*uuid.UUID"
|
||||
}
|
||||
return "uuid.NullUUID"
|
||||
|
||||
case "inet":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV5:
|
||||
if notNull {
|
||||
return "netip.Addr"
|
||||
}
|
||||
return "*netip.Addr"
|
||||
case opts.SQLDriverPGXV4:
|
||||
return "pgtype.Inet"
|
||||
case opts.SQLDriverLibPQ:
|
||||
return "pqtype.Inet"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "cidr":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV5:
|
||||
if notNull {
|
||||
return "netip.Prefix"
|
||||
}
|
||||
return "*netip.Prefix"
|
||||
case opts.SQLDriverPGXV4:
|
||||
return "pgtype.CIDR"
|
||||
case opts.SQLDriverLibPQ:
|
||||
return "pqtype.CIDR"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "macaddr", "macaddr8":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "net.HardwareAddr"
|
||||
case opts.SQLDriverPGXV4:
|
||||
return "pgtype.Macaddr"
|
||||
case opts.SQLDriverLibPQ:
|
||||
return "pqtype.Macaddr"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "ltree", "lquery", "ltxtquery":
|
||||
// This module implements a data type ltree for representing labels
|
||||
// of data stored in a hierarchical tree-like structure. Extensive
|
||||
// facilities for searching through label trees are provided.
|
||||
//
|
||||
// https://www.postgresql.org/docs/current/ltree.html
|
||||
if notNull {
|
||||
return "string"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*string"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Text"
|
||||
}
|
||||
return "sql.NullString"
|
||||
|
||||
case "interval", "pg_catalog.interval":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Interval"
|
||||
}
|
||||
if notNull {
|
||||
return "int64"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*int64"
|
||||
}
|
||||
return "sql.NullInt64"
|
||||
|
||||
case "daterange":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV4:
|
||||
return "pgtype.Daterange"
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Range[pgtype.Date]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "datemultirange":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Multirange[pgtype.Range[pgtype.Date]]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "tsrange":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV4:
|
||||
return "pgtype.Tsrange"
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Range[pgtype.Timestamp]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "tsmultirange":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Multirange[pgtype.Range[pgtype.Timestamp]]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "tstzrange":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV4:
|
||||
return "pgtype.Tstzrange"
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Range[pgtype.Timestamptz]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "tstzmultirange":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Multirange[pgtype.Range[pgtype.Timestamptz]]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "numrange":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV4:
|
||||
return "pgtype.Numrange"
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Range[pgtype.Numeric]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "nummultirange":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Multirange[pgtype.Range[pgtype.Numeric]]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "int4range":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV4:
|
||||
return "pgtype.Int4range"
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Range[pgtype.Int4]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "int4multirange":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Multirange[pgtype.Range[pgtype.Int4]]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "int8range":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV4:
|
||||
return "pgtype.Int8range"
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Range[pgtype.Int8]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "int8multirange":
|
||||
switch driver {
|
||||
case opts.SQLDriverPGXV5:
|
||||
return "pgtype.Multirange[pgtype.Range[pgtype.Int8]]"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
|
||||
case "hstore":
|
||||
if driver.IsPGX() {
|
||||
return "pgtype.Hstore"
|
||||
}
|
||||
return "any"
|
||||
|
||||
case "bit", "varbit", "bit varying", "pg_catalog.bit", "pg_catalog.varbit":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Bits"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV4 {
|
||||
return "pgtype.Varbit"
|
||||
}
|
||||
|
||||
case "cid":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Uint32"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV4 {
|
||||
return "pgtype.CID"
|
||||
}
|
||||
|
||||
case "oid":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Uint32"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV4 {
|
||||
return "pgtype.OID"
|
||||
}
|
||||
|
||||
case "tid":
|
||||
if driver.IsPGX() {
|
||||
return "pgtype.TID"
|
||||
}
|
||||
|
||||
case "xid":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Uint32"
|
||||
}
|
||||
if driver == opts.SQLDriverPGXV4 {
|
||||
return "pgtype.XID"
|
||||
}
|
||||
|
||||
case "xid8":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
return "pgtype.Uint64"
|
||||
}
|
||||
|
||||
case "box":
|
||||
if driver.IsPGX() {
|
||||
return "pgtype.Box"
|
||||
}
|
||||
|
||||
case "circle":
|
||||
if driver.IsPGX() {
|
||||
return "pgtype.Circle"
|
||||
}
|
||||
|
||||
case "line":
|
||||
if driver.IsPGX() {
|
||||
return "pgtype.Line"
|
||||
}
|
||||
|
||||
case "lseg":
|
||||
if driver.IsPGX() {
|
||||
return "pgtype.Lseg"
|
||||
}
|
||||
|
||||
case "path":
|
||||
if driver.IsPGX() {
|
||||
return "pgtype.Path"
|
||||
}
|
||||
|
||||
case "point":
|
||||
if driver.IsPGX() {
|
||||
return "pgtype.Point"
|
||||
}
|
||||
|
||||
case "polygon":
|
||||
if driver.IsPGX() {
|
||||
return "pgtype.Polygon"
|
||||
}
|
||||
|
||||
case "vector":
|
||||
if driver == opts.SQLDriverPGXV5 {
|
||||
if emitPointersForNull {
|
||||
return "*pgvector.Vector"
|
||||
} else {
|
||||
return "pgvector.Vector"
|
||||
}
|
||||
}
|
||||
|
||||
case "void":
|
||||
// A void value can only be scanned into an empty interface.
|
||||
return "any"
|
||||
|
||||
case "any":
|
||||
return "any"
|
||||
|
||||
default:
|
||||
rel, err := parseIdentifierString(columnType)
|
||||
if err != nil {
|
||||
// TODO: Should this actually return an error here?
|
||||
return "any"
|
||||
}
|
||||
if rel.Schema == "" {
|
||||
rel.Schema = req.Catalog.DefaultSchema
|
||||
}
|
||||
|
||||
for _, schema := range req.Catalog.Schemas {
|
||||
if schema.Name == "pg_catalog" || schema.Name == "information_schema" {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, enum := range schema.Enums {
|
||||
if rel.Name == enum.Name && rel.Schema == schema.Name {
|
||||
if notNull {
|
||||
if schema.Name == req.Catalog.DefaultSchema {
|
||||
return StructName(enum.Name, options)
|
||||
}
|
||||
return StructName(schema.Name+"_"+enum.Name, options)
|
||||
} else {
|
||||
nullPrefix := "Null"
|
||||
if emitPointersForNullEnums {
|
||||
nullPrefix = "*"
|
||||
}
|
||||
if schema.Name == req.Catalog.DefaultSchema {
|
||||
return nullPrefix + StructName(enum.Name, options)
|
||||
}
|
||||
return nullPrefix + StructName(schema.Name+"_"+enum.Name, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ct := range schema.CompositeTypes {
|
||||
if rel.Name == ct.Name && rel.Schema == schema.Name {
|
||||
if notNull {
|
||||
return "string"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*string"
|
||||
}
|
||||
return "sql.NullString"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if debug.Active {
|
||||
log.Printf("unknown PostgreSQL type: %s\n", columnType)
|
||||
}
|
||||
return "any"
|
||||
}
|
||||
79
internal/codegen/golang/qualify.go
Normal file
79
internal/codegen/golang/qualify.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package golang
|
||||
|
||||
import "strings"
|
||||
|
||||
// stripQualifier removes a leading slice/pointer prefix and the given
|
||||
// `pkg.` qualifier from a Go type expression. When the qualifier is empty
|
||||
// or absent from the type, the input is returned unchanged.
|
||||
func stripQualifier(t, qualifier string) string {
|
||||
if qualifier == "" {
|
||||
return t
|
||||
}
|
||||
var prefix strings.Builder
|
||||
rest := t
|
||||
for {
|
||||
if strings.HasPrefix(rest, "[]") {
|
||||
prefix.WriteString("[]")
|
||||
rest = rest[2:]
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(rest, "*") {
|
||||
prefix.WriteString("*")
|
||||
rest = rest[1:]
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if strings.HasPrefix(rest, qualifier) {
|
||||
return prefix.String() + rest[len(qualifier):]
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// modelTypeSet is the set of Go type names that live in the models file.
|
||||
type modelTypeSet map[string]struct{}
|
||||
|
||||
// buildModelTypeSet returns the set of type names that are declared in
|
||||
// models.go for the current codegen invocation.
|
||||
func buildModelTypeSet(enums []Enum, structs []Struct) modelTypeSet {
|
||||
set := make(modelTypeSet, len(enums)*4+len(structs))
|
||||
for _, e := range enums {
|
||||
set[e.Name] = struct{}{}
|
||||
set["Null"+e.Name] = struct{}{}
|
||||
}
|
||||
for _, s := range structs {
|
||||
if s.IsModel {
|
||||
set[s.Name] = struct{}{}
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// qualifyType prefixes a Go type expression with `qualifier` when the bare
|
||||
// type name belongs to `models`. Slice and pointer prefixes are preserved.
|
||||
// When qualifier is empty (i.e. models live in the queries package), the
|
||||
// input is returned unchanged.
|
||||
func qualifyType(t string, models modelTypeSet, qualifier string) string {
|
||||
if qualifier == "" || t == "" || len(models) == 0 {
|
||||
return t
|
||||
}
|
||||
var prefix strings.Builder
|
||||
rest := t
|
||||
for {
|
||||
if strings.HasPrefix(rest, "[]") {
|
||||
prefix.WriteString("[]")
|
||||
rest = rest[2:]
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(rest, "*") {
|
||||
prefix.WriteString("*")
|
||||
rest = rest[1:]
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if _, ok := models[rest]; ok {
|
||||
return prefix.String() + qualifier + rest
|
||||
}
|
||||
return t
|
||||
}
|
||||
308
internal/codegen/golang/query.go
Normal file
308
internal/codegen/golang/query.go
Normal file
@@ -0,0 +1,308 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
|
||||
"github.com/sqlc-dev/sqlc/internal/metadata"
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
type QueryValue struct {
|
||||
Emit bool
|
||||
EmitPointer bool
|
||||
Name string
|
||||
DBName string // The name of the field in the database. Only set if Struct==nil.
|
||||
Struct *Struct
|
||||
Typ string
|
||||
SQLDriver opts.SQLDriver
|
||||
|
||||
// ModelQualifier prefixes references to model types when the models file
|
||||
// lives in a different Go package (e.g. "model."). Empty otherwise.
|
||||
ModelQualifier string
|
||||
|
||||
// Column is kept so late in the generation process around to differentiate
|
||||
// between mysql slices and pg arrays
|
||||
Column *plugin.Column
|
||||
}
|
||||
|
||||
func (v QueryValue) EmitStruct() bool {
|
||||
return v.Emit
|
||||
}
|
||||
|
||||
func (v QueryValue) IsStruct() bool {
|
||||
return v.Struct != nil
|
||||
}
|
||||
|
||||
func (v QueryValue) IsPointer() bool {
|
||||
return v.EmitPointer && v.Struct != nil
|
||||
}
|
||||
|
||||
func (v QueryValue) isEmpty() bool {
|
||||
return v.Typ == "" && v.Name == "" && v.Struct == nil
|
||||
}
|
||||
|
||||
type Argument struct {
|
||||
Name string
|
||||
Type string
|
||||
}
|
||||
|
||||
func (v QueryValue) Pair() string {
|
||||
var out []string
|
||||
for _, arg := range v.Pairs() {
|
||||
out = append(out, arg.Name+" "+arg.Type)
|
||||
}
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
|
||||
// Return the argument name and type for query methods. Should only be used in
|
||||
// the context of method arguments.
|
||||
func (v QueryValue) Pairs() []Argument {
|
||||
if v.isEmpty() {
|
||||
return nil
|
||||
}
|
||||
if !v.EmitStruct() && v.IsStruct() {
|
||||
var out []Argument
|
||||
for _, f := range v.Struct.Fields {
|
||||
out = append(out, Argument{
|
||||
Name: escape(toLowerCase(f.Name)),
|
||||
Type: f.Type,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
return []Argument{
|
||||
{
|
||||
Name: escape(v.Name),
|
||||
Type: v.DefineType(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v QueryValue) SlicePair() string {
|
||||
if v.isEmpty() {
|
||||
return ""
|
||||
}
|
||||
return v.Name + " []" + v.DefineType()
|
||||
}
|
||||
|
||||
func (v QueryValue) Type() string {
|
||||
if v.Typ != "" {
|
||||
return v.Typ
|
||||
}
|
||||
if v.Struct != nil {
|
||||
// Model structs (table-derived) live in the models file. When that
|
||||
// file is generated into a different Go package, references from
|
||||
// query files must be qualified. Synthetic structs (Params/Row)
|
||||
// are defined in the same query file as their use, so they stay
|
||||
// bare.
|
||||
if v.Struct.IsModel && v.ModelQualifier != "" {
|
||||
return v.ModelQualifier + v.Struct.Name
|
||||
}
|
||||
return v.Struct.Name
|
||||
}
|
||||
panic("no type for QueryValue: " + v.Name)
|
||||
}
|
||||
|
||||
func (v *QueryValue) DefineType() string {
|
||||
t := v.Type()
|
||||
if v.IsPointer() {
|
||||
return "*" + t
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (v *QueryValue) ReturnName() string {
|
||||
if v.IsPointer() {
|
||||
return "&" + escape(v.Name)
|
||||
}
|
||||
return escape(v.Name)
|
||||
}
|
||||
|
||||
func (v QueryValue) UniqueFields() []Field {
|
||||
seen := map[string]struct{}{}
|
||||
fields := make([]Field, 0, len(v.Struct.Fields))
|
||||
|
||||
for _, field := range v.Struct.Fields {
|
||||
if _, found := seen[field.Name]; found {
|
||||
continue
|
||||
}
|
||||
seen[field.Name] = struct{}{}
|
||||
fields = append(fields, field)
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func (v QueryValue) Params() string {
|
||||
if v.isEmpty() {
|
||||
return ""
|
||||
}
|
||||
var out []string
|
||||
if v.Struct == nil {
|
||||
if !v.Column.IsSqlcSlice && strings.HasPrefix(v.Typ, "[]") && v.Typ != "[]byte" && !v.SQLDriver.IsPGX() {
|
||||
out = append(out, "pq.Array("+escape(v.Name)+")")
|
||||
} else {
|
||||
out = append(out, escape(v.Name))
|
||||
}
|
||||
} else {
|
||||
for _, f := range v.Struct.Fields {
|
||||
if !f.HasSqlcSlice() && strings.HasPrefix(f.Type, "[]") && f.Type != "[]byte" && !v.SQLDriver.IsPGX() {
|
||||
out = append(out, "pq.Array("+escape(v.VariableForField(f))+")")
|
||||
} else {
|
||||
out = append(out, escape(v.VariableForField(f)))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(out) <= 3 {
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
out = append(out, "")
|
||||
return "\n" + strings.Join(out, ",\n")
|
||||
}
|
||||
|
||||
func (v QueryValue) ColumnNames() []string {
|
||||
if v.Struct == nil {
|
||||
return []string{v.DBName}
|
||||
}
|
||||
names := make([]string, len(v.Struct.Fields))
|
||||
for i, f := range v.Struct.Fields {
|
||||
names[i] = f.DBName
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (v QueryValue) ColumnNamesAsGoSlice() string {
|
||||
if v.Struct == nil {
|
||||
return fmt.Sprintf("[]string{%q}", v.DBName)
|
||||
}
|
||||
escapedNames := make([]string, len(v.Struct.Fields))
|
||||
for i, f := range v.Struct.Fields {
|
||||
if f.Column != nil && f.Column.OriginalName != "" {
|
||||
escapedNames[i] = fmt.Sprintf("%q", f.Column.OriginalName)
|
||||
} else {
|
||||
escapedNames[i] = fmt.Sprintf("%q", f.DBName)
|
||||
}
|
||||
}
|
||||
return "[]string{" + strings.Join(escapedNames, ", ") + "}"
|
||||
}
|
||||
|
||||
// When true, we have to build the arguments to q.db.QueryContext in addition to
|
||||
// munging the SQL
|
||||
func (v QueryValue) HasSqlcSlices() bool {
|
||||
if v.Struct == nil {
|
||||
return v.Column != nil && v.Column.IsSqlcSlice
|
||||
}
|
||||
for _, v := range v.Struct.Fields {
|
||||
if v.Column.IsSqlcSlice {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (v QueryValue) Scan() string {
|
||||
var out []string
|
||||
if v.Struct == nil {
|
||||
if strings.HasPrefix(v.Typ, "[]") && v.Typ != "[]byte" && !v.SQLDriver.IsPGX() {
|
||||
out = append(out, "pq.Array(&"+v.Name+")")
|
||||
} else {
|
||||
out = append(out, "&"+v.Name)
|
||||
}
|
||||
} else {
|
||||
for _, f := range v.Struct.Fields {
|
||||
|
||||
// append any embedded fields
|
||||
if len(f.EmbedFields) > 0 {
|
||||
for _, embed := range f.EmbedFields {
|
||||
if strings.HasPrefix(embed.Type, "[]") && embed.Type != "[]byte" && !v.SQLDriver.IsPGX() {
|
||||
out = append(out, "pq.Array(&"+v.Name+"."+f.Name+"."+embed.Name+")")
|
||||
} else {
|
||||
out = append(out, "&"+v.Name+"."+f.Name+"."+embed.Name)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(f.Type, "[]") && f.Type != "[]byte" && !v.SQLDriver.IsPGX() {
|
||||
out = append(out, "pq.Array(&"+v.Name+"."+f.Name+")")
|
||||
} else {
|
||||
out = append(out, "&"+v.Name+"."+f.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(out) <= 3 {
|
||||
return strings.Join(out, ",")
|
||||
}
|
||||
out = append(out, "")
|
||||
return "\n" + strings.Join(out, ",\n")
|
||||
}
|
||||
|
||||
// Deprecated: This method does not respect the Emit field set on the
|
||||
// QueryValue. It's used by the go-sql-driver-mysql/copyfromCopy.tmpl and should
|
||||
// not be used other places.
|
||||
func (v QueryValue) CopyFromMySQLFields() []Field {
|
||||
// fmt.Printf("%#v\n", v)
|
||||
if v.Struct != nil {
|
||||
return v.Struct.Fields
|
||||
}
|
||||
return []Field{
|
||||
{
|
||||
Name: v.Name,
|
||||
DBName: v.DBName,
|
||||
Type: v.Typ,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (v QueryValue) VariableForField(f Field) string {
|
||||
if !v.IsStruct() {
|
||||
return v.Name
|
||||
}
|
||||
if !v.EmitStruct() {
|
||||
return toLowerCase(f.Name)
|
||||
}
|
||||
return v.Name + "." + f.Name
|
||||
}
|
||||
|
||||
// A struct used to generate methods and fields on the Queries struct
|
||||
type Query struct {
|
||||
Cmd string
|
||||
Comments []string
|
||||
MethodName string
|
||||
FieldName string
|
||||
ConstantName string
|
||||
SQL string
|
||||
SourceName string
|
||||
Ret QueryValue
|
||||
Arg QueryValue
|
||||
// Used for :copyfrom
|
||||
Table *plugin.Identifier
|
||||
}
|
||||
|
||||
func (q Query) hasRetType() bool {
|
||||
scanned := q.Cmd == metadata.CmdOne || q.Cmd == metadata.CmdMany ||
|
||||
q.Cmd == metadata.CmdBatchMany || q.Cmd == metadata.CmdBatchOne
|
||||
return scanned && !q.Ret.isEmpty()
|
||||
}
|
||||
|
||||
func (q Query) TableIdentifierAsGoSlice() string {
|
||||
escapedNames := make([]string, 0, 3)
|
||||
for _, p := range []string{q.Table.Catalog, q.Table.Schema, q.Table.Name} {
|
||||
if p != "" {
|
||||
escapedNames = append(escapedNames, fmt.Sprintf("%q", p))
|
||||
}
|
||||
}
|
||||
return "[]string{" + strings.Join(escapedNames, ", ") + "}"
|
||||
}
|
||||
|
||||
func (q Query) TableIdentifierForMySQL() string {
|
||||
escapedNames := make([]string, 0, 3)
|
||||
for _, p := range []string{q.Table.Catalog, q.Table.Schema, q.Table.Name} {
|
||||
if p != "" {
|
||||
escapedNames = append(escapedNames, fmt.Sprintf("`%s`", p))
|
||||
}
|
||||
}
|
||||
return strings.Join(escapedNames, ".")
|
||||
}
|
||||
71
internal/codegen/golang/reserved.go
Normal file
71
internal/codegen/golang/reserved.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package golang
|
||||
|
||||
func escape(s string) string {
|
||||
if IsReserved(s) {
|
||||
return s + "_"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func IsReserved(s string) bool {
|
||||
switch s {
|
||||
// Not a keyword, but generated code refers to the predeclared type
|
||||
// `any`, so a package-scope identifier with that name would shadow it.
|
||||
case "any":
|
||||
return true
|
||||
case "break":
|
||||
return true
|
||||
case "default":
|
||||
return true
|
||||
case "func":
|
||||
return true
|
||||
case "interface":
|
||||
return true
|
||||
case "select":
|
||||
return true
|
||||
case "case":
|
||||
return true
|
||||
case "defer":
|
||||
return true
|
||||
case "go":
|
||||
return true
|
||||
case "map":
|
||||
return true
|
||||
case "struct":
|
||||
return true
|
||||
case "chan":
|
||||
return true
|
||||
case "else":
|
||||
return true
|
||||
case "goto":
|
||||
return true
|
||||
case "package":
|
||||
return true
|
||||
case "switch":
|
||||
return true
|
||||
case "const":
|
||||
return true
|
||||
case "fallthrough":
|
||||
return true
|
||||
case "if":
|
||||
return true
|
||||
case "range":
|
||||
return true
|
||||
case "type":
|
||||
return true
|
||||
case "continue":
|
||||
return true
|
||||
case "for":
|
||||
return true
|
||||
case "import":
|
||||
return true
|
||||
case "return":
|
||||
return true
|
||||
case "var":
|
||||
return true
|
||||
case "q":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
473
internal/codegen/golang/result.go
Normal file
473
internal/codegen/golang/result.go
Normal file
@@ -0,0 +1,473 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/sdk"
|
||||
"github.com/sqlc-dev/sqlc/internal/inflection"
|
||||
"github.com/sqlc-dev/sqlc/internal/metadata"
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
func buildEnums(req *plugin.GenerateRequest, options *opts.Options) []Enum {
|
||||
var enums []Enum
|
||||
for _, schema := range req.Catalog.Schemas {
|
||||
if schema.Name == "pg_catalog" || schema.Name == "information_schema" {
|
||||
continue
|
||||
}
|
||||
for _, enum := range schema.Enums {
|
||||
var enumName string
|
||||
if schema.Name == req.Catalog.DefaultSchema {
|
||||
enumName = enum.Name
|
||||
} else {
|
||||
enumName = schema.Name + "_" + enum.Name
|
||||
}
|
||||
|
||||
e := Enum{
|
||||
Name: StructName(enumName, options),
|
||||
Comment: enum.Comment,
|
||||
NameTags: map[string]string{},
|
||||
ValidTags: map[string]string{},
|
||||
}
|
||||
if options.EmitJsonTags {
|
||||
e.NameTags["json"] = JSONTagName(enumName, options)
|
||||
e.ValidTags["json"] = JSONTagName("valid", options)
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, len(enum.Vals))
|
||||
for i, v := range enum.Vals {
|
||||
value := EnumReplace(v)
|
||||
if _, found := seen[value]; found || value == "" {
|
||||
value = fmt.Sprintf("value_%d", i)
|
||||
}
|
||||
e.Constants = append(e.Constants, Constant{
|
||||
Name: StructName(enumName+"_"+value, options),
|
||||
Value: v,
|
||||
Type: e.Name,
|
||||
})
|
||||
seen[value] = struct{}{}
|
||||
}
|
||||
enums = append(enums, e)
|
||||
}
|
||||
}
|
||||
if len(enums) > 0 {
|
||||
sort.Slice(enums, func(i, j int) bool { return enums[i].Name < enums[j].Name })
|
||||
}
|
||||
return enums
|
||||
}
|
||||
|
||||
func buildStructs(req *plugin.GenerateRequest, options *opts.Options) []Struct {
|
||||
var structs []Struct
|
||||
for _, schema := range req.Catalog.Schemas {
|
||||
if schema.Name == "pg_catalog" || schema.Name == "information_schema" {
|
||||
continue
|
||||
}
|
||||
for _, table := range schema.Tables {
|
||||
var tableName string
|
||||
if schema.Name == req.Catalog.DefaultSchema {
|
||||
tableName = table.Rel.Name
|
||||
} else {
|
||||
tableName = schema.Name + "_" + table.Rel.Name
|
||||
}
|
||||
structName := tableName
|
||||
if !options.EmitExactTableNames {
|
||||
structName = inflection.Singular(inflection.SingularParams{
|
||||
Name: structName,
|
||||
Exclusions: options.InflectionExcludeTableNames,
|
||||
})
|
||||
}
|
||||
s := Struct{
|
||||
Table: &plugin.Identifier{Schema: schema.Name, Name: table.Rel.Name},
|
||||
Name: StructName(structName, options),
|
||||
Comment: table.Comment,
|
||||
IsModel: true,
|
||||
}
|
||||
for _, column := range table.Columns {
|
||||
tags := map[string]string{}
|
||||
if options.EmitDbTags {
|
||||
tags["db"] = column.Name
|
||||
}
|
||||
if options.EmitJsonTags {
|
||||
tags["json"] = JSONTagName(column.Name, options)
|
||||
}
|
||||
addExtraGoStructTags(tags, req, options, column)
|
||||
s.Fields = append(s.Fields, Field{
|
||||
Name: StructName(column.Name, options),
|
||||
Type: goType(req, options, column),
|
||||
Tags: tags,
|
||||
Comment: column.Comment,
|
||||
})
|
||||
}
|
||||
structs = append(structs, s)
|
||||
}
|
||||
}
|
||||
if len(structs) > 0 {
|
||||
sort.Slice(structs, func(i, j int) bool { return structs[i].Name < structs[j].Name })
|
||||
}
|
||||
return structs
|
||||
}
|
||||
|
||||
type goColumn struct {
|
||||
id int
|
||||
*plugin.Column
|
||||
embed *goEmbed
|
||||
}
|
||||
|
||||
type goEmbed struct {
|
||||
modelType string
|
||||
modelName string
|
||||
fields []Field
|
||||
}
|
||||
|
||||
// look through all the structs and attempt to find a matching one to embed
|
||||
// We need the name of the struct and its field names.
|
||||
func newGoEmbed(embed *plugin.Identifier, structs []Struct, defaultSchema string) *goEmbed {
|
||||
if embed == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, s := range structs {
|
||||
embedSchema := defaultSchema
|
||||
if embed.Schema != "" {
|
||||
embedSchema = embed.Schema
|
||||
}
|
||||
|
||||
// compare the other attributes
|
||||
if embed.Catalog != s.Table.Catalog || embed.Name != s.Table.Name || embedSchema != s.Table.Schema {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := make([]Field, len(s.Fields))
|
||||
copy(fields, s.Fields)
|
||||
|
||||
return &goEmbed{
|
||||
modelType: s.Name,
|
||||
modelName: s.Name,
|
||||
fields: fields,
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func columnName(c *plugin.Column, pos int) string {
|
||||
if c.Name != "" {
|
||||
return c.Name
|
||||
}
|
||||
return fmt.Sprintf("column_%d", pos+1)
|
||||
}
|
||||
|
||||
func paramName(p *plugin.Parameter) string {
|
||||
if p.Column.Name != "" {
|
||||
return argName(p.Column.Name)
|
||||
}
|
||||
return fmt.Sprintf("dollar_%d", p.Number)
|
||||
}
|
||||
|
||||
func argName(name string) string {
|
||||
var out strings.Builder
|
||||
for i, p := range strings.Split(name, "_") {
|
||||
if i == 0 {
|
||||
out.WriteString(strings.ToLower(p))
|
||||
} else if p == "id" {
|
||||
out.WriteString("ID")
|
||||
} else {
|
||||
out.WriteString(strings.Title(p))
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func buildQueries(req *plugin.GenerateRequest, options *opts.Options, enums []Enum, structs []Struct) ([]Query, error) {
|
||||
models := buildModelTypeSet(enums, structs)
|
||||
qualifier := options.ModelsTypeQualifier()
|
||||
qs := make([]Query, 0, len(req.Queries))
|
||||
for _, query := range req.Queries {
|
||||
if query.Name == "" {
|
||||
continue
|
||||
}
|
||||
if query.Cmd == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var constantName string
|
||||
if options.EmitExportedQueries {
|
||||
constantName = sdk.Title(query.Name)
|
||||
} else {
|
||||
constantName = escape(sdk.LowerTitle(query.Name))
|
||||
}
|
||||
|
||||
comments := query.Comments
|
||||
if options.EmitSqlAsComment {
|
||||
if len(comments) == 0 {
|
||||
comments = append(comments, query.Name)
|
||||
}
|
||||
comments = append(comments, " ")
|
||||
scanner := bufio.NewScanner(strings.NewReader(query.Text))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
comments = append(comments, " "+line)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
gq := Query{
|
||||
Cmd: query.Cmd,
|
||||
ConstantName: constantName,
|
||||
FieldName: sdk.LowerTitle(query.Name) + "Stmt",
|
||||
MethodName: query.Name,
|
||||
SourceName: query.Filename,
|
||||
SQL: query.Text,
|
||||
Comments: comments,
|
||||
Table: query.InsertIntoTable,
|
||||
}
|
||||
sqlpkg := parseDriver(options.SqlPackage)
|
||||
|
||||
qpl := int(*options.QueryParameterLimit)
|
||||
|
||||
if len(query.Params) == 1 && qpl != 0 {
|
||||
p := query.Params[0]
|
||||
gq.Arg = QueryValue{
|
||||
Name: escape(paramName(p)),
|
||||
DBName: p.Column.GetName(),
|
||||
Typ: qualifyType(goType(req, options, p.Column), models, qualifier),
|
||||
SQLDriver: sqlpkg,
|
||||
ModelQualifier: qualifier,
|
||||
Column: p.Column,
|
||||
}
|
||||
} else if len(query.Params) >= 1 {
|
||||
var cols []goColumn
|
||||
for _, p := range query.Params {
|
||||
cols = append(cols, goColumn{
|
||||
id: int(p.Number),
|
||||
Column: p.Column,
|
||||
})
|
||||
}
|
||||
s, err := columnsToStruct(req, options, gq.MethodName+"Params", cols, false, models, qualifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gq.Arg = QueryValue{
|
||||
Emit: true,
|
||||
Name: "arg",
|
||||
Struct: s,
|
||||
SQLDriver: sqlpkg,
|
||||
EmitPointer: options.EmitParamsStructPointers,
|
||||
ModelQualifier: qualifier,
|
||||
}
|
||||
|
||||
// if query params is 2, and query params limit is 4 AND this is a copyfrom, we still want to emit the query's model
|
||||
// otherwise we end up with a copyfrom using a struct without the struct definition
|
||||
if len(query.Params) <= qpl && query.Cmd != ":copyfrom" {
|
||||
gq.Arg.Emit = false
|
||||
}
|
||||
}
|
||||
|
||||
if len(query.Columns) == 1 && query.Columns[0].EmbedTable == nil {
|
||||
c := query.Columns[0]
|
||||
name := columnName(c, 0)
|
||||
name = strings.Replace(name, "$", "_", -1)
|
||||
retName := escape(name)
|
||||
// For :one queries the scan destination lives in the same scope as
|
||||
// the query parameters, so reusing a parameter's name would cause
|
||||
// Scan to overwrite the input and leak it back to the caller on
|
||||
// sql.ErrNoRows (see sqlc-dev/sqlc#4354). Rename the return
|
||||
// variable when it would collide.
|
||||
if query.Cmd == metadata.CmdOne {
|
||||
argNames := map[string]struct{}{}
|
||||
for _, p := range gq.Arg.Pairs() {
|
||||
argNames[p.Name] = struct{}{}
|
||||
}
|
||||
for {
|
||||
if _, conflict := argNames[retName]; !conflict {
|
||||
break
|
||||
}
|
||||
retName += "_2"
|
||||
}
|
||||
}
|
||||
gq.Ret = QueryValue{
|
||||
Name: retName,
|
||||
DBName: name,
|
||||
Typ: qualifyType(goType(req, options, c), models, qualifier),
|
||||
SQLDriver: sqlpkg,
|
||||
ModelQualifier: qualifier,
|
||||
}
|
||||
} else if putOutColumns(query) {
|
||||
var gs *Struct
|
||||
var emit bool
|
||||
|
||||
for _, s := range structs {
|
||||
if len(s.Fields) != len(query.Columns) {
|
||||
continue
|
||||
}
|
||||
same := true
|
||||
for i, f := range s.Fields {
|
||||
c := query.Columns[i]
|
||||
sameName := f.Name == StructName(columnName(c, i), options)
|
||||
sameType := f.Type == goType(req, options, c)
|
||||
sameTable := sdk.SameTableName(c.Table, s.Table, req.Catalog.DefaultSchema)
|
||||
if !sameName || !sameType || !sameTable {
|
||||
same = false
|
||||
}
|
||||
}
|
||||
if same {
|
||||
gs = &s
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if gs == nil {
|
||||
var columns []goColumn
|
||||
for i, c := range query.Columns {
|
||||
columns = append(columns, goColumn{
|
||||
id: i,
|
||||
Column: c,
|
||||
embed: newGoEmbed(c.EmbedTable, structs, req.Catalog.DefaultSchema),
|
||||
})
|
||||
}
|
||||
var err error
|
||||
gs, err = columnsToStruct(req, options, gq.MethodName+"Row", columns, true, models, qualifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
emit = true
|
||||
}
|
||||
gq.Ret = QueryValue{
|
||||
Emit: emit,
|
||||
Name: "i",
|
||||
Struct: gs,
|
||||
SQLDriver: sqlpkg,
|
||||
EmitPointer: options.EmitResultStructPointers,
|
||||
ModelQualifier: qualifier,
|
||||
}
|
||||
}
|
||||
|
||||
qs = append(qs, gq)
|
||||
}
|
||||
sort.Slice(qs, func(i, j int) bool { return qs[i].MethodName < qs[j].MethodName })
|
||||
return qs, nil
|
||||
}
|
||||
|
||||
var cmdReturnsData = map[string]struct{}{
|
||||
metadata.CmdBatchMany: {},
|
||||
metadata.CmdBatchOne: {},
|
||||
metadata.CmdMany: {},
|
||||
metadata.CmdOne: {},
|
||||
}
|
||||
|
||||
func putOutColumns(query *plugin.Query) bool {
|
||||
_, found := cmdReturnsData[query.Cmd]
|
||||
return found
|
||||
}
|
||||
|
||||
// It's possible that this method will generate duplicate JSON tag values
|
||||
//
|
||||
// Columns: count, count, count_2
|
||||
// Fields: Count, Count_2, Count2
|
||||
//
|
||||
// JSON tags: count, count_2, count_2
|
||||
//
|
||||
// This is unlikely to happen, so don't fix it yet
|
||||
func columnsToStruct(req *plugin.GenerateRequest, options *opts.Options, name string, columns []goColumn, useID bool, models modelTypeSet, qualifier string) (*Struct, error) {
|
||||
gs := Struct{
|
||||
Name: name,
|
||||
}
|
||||
seen := map[string][]int{}
|
||||
suffixes := map[int]int{}
|
||||
for i, c := range columns {
|
||||
colName := columnName(c.Column, i)
|
||||
tagName := colName
|
||||
|
||||
// override col/tag with expected model name
|
||||
if c.embed != nil {
|
||||
colName = c.embed.modelName
|
||||
tagName = SetCaseStyle(colName, "snake")
|
||||
}
|
||||
|
||||
fieldName := StructName(colName, options)
|
||||
baseFieldName := fieldName
|
||||
// Track suffixes by the ID of the column, so that columns referring to the same numbered parameter can be
|
||||
// reused.
|
||||
suffix := 0
|
||||
if o, ok := suffixes[c.id]; ok && useID {
|
||||
suffix = o
|
||||
} else if v := len(seen[fieldName]); v > 0 && !c.IsNamedParam {
|
||||
suffix = v + 1
|
||||
}
|
||||
suffixes[c.id] = suffix
|
||||
if suffix > 0 {
|
||||
tagName = fmt.Sprintf("%s_%d", tagName, suffix)
|
||||
fieldName = fmt.Sprintf("%s_%d", fieldName, suffix)
|
||||
}
|
||||
tags := map[string]string{}
|
||||
if options.EmitDbTags {
|
||||
tags["db"] = tagName
|
||||
}
|
||||
if options.EmitJsonTags {
|
||||
tags["json"] = JSONTagName(tagName, options)
|
||||
}
|
||||
addExtraGoStructTags(tags, req, options, c.Column)
|
||||
f := Field{
|
||||
Name: fieldName,
|
||||
DBName: colName,
|
||||
Tags: tags,
|
||||
Column: c.Column,
|
||||
}
|
||||
if c.embed == nil {
|
||||
f.Type = qualifyType(goType(req, options, c.Column), models, qualifier)
|
||||
} else {
|
||||
f.Type = qualifyType(c.embed.modelType, models, qualifier)
|
||||
f.EmbedFields = c.embed.fields
|
||||
}
|
||||
|
||||
gs.Fields = append(gs.Fields, f)
|
||||
if _, found := seen[baseFieldName]; !found {
|
||||
seen[baseFieldName] = []int{i}
|
||||
} else {
|
||||
seen[baseFieldName] = append(seen[baseFieldName], i)
|
||||
}
|
||||
}
|
||||
|
||||
// If a field does not have a known type, but another
|
||||
// field with the same name has a known type, assign
|
||||
// the known type to the field without a known type
|
||||
for i, field := range gs.Fields {
|
||||
if len(seen[field.Name]) > 1 && field.Type == "any" {
|
||||
for _, j := range seen[field.Name] {
|
||||
if i == j {
|
||||
continue
|
||||
}
|
||||
otherField := gs.Fields[j]
|
||||
if otherField.Type != field.Type {
|
||||
field.Type = otherField.Type
|
||||
}
|
||||
gs.Fields[i] = field
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := checkIncompatibleFieldTypes(gs.Fields)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gs, nil
|
||||
}
|
||||
|
||||
func checkIncompatibleFieldTypes(fields []Field) error {
|
||||
fieldTypes := map[string]string{}
|
||||
for _, field := range fields {
|
||||
if fieldType, found := fieldTypes[field.Name]; !found {
|
||||
fieldTypes[field.Name] = field.Type
|
||||
} else if field.Type != fieldType {
|
||||
return fmt.Errorf("named param %s has incompatible types: %s, %s", field.Name, field.Type, fieldType)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
78
internal/codegen/golang/result_test.go
Normal file
78
internal/codegen/golang/result_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/metadata"
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
func TestPutOutColumns_ForZeroColumns(t *testing.T) {
|
||||
tests := []struct {
|
||||
cmd string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
cmd: metadata.CmdExec,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
cmd: metadata.CmdExecResult,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
cmd: metadata.CmdExecRows,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
cmd: metadata.CmdExecLastId,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
cmd: metadata.CmdMany,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
cmd: metadata.CmdOne,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
cmd: metadata.CmdCopyFrom,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
cmd: metadata.CmdBatchExec,
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
cmd: metadata.CmdBatchMany,
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
cmd: metadata.CmdBatchOne,
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.cmd, func(t *testing.T) {
|
||||
query := &plugin.Query{
|
||||
Cmd: tc.cmd,
|
||||
Columns: []*plugin.Column{},
|
||||
}
|
||||
got := putOutColumns(query)
|
||||
if got != tc.want {
|
||||
t.Errorf("putOutColumns failed. want %v, got %v", tc.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPutOutColumns_AlwaysTrueWhenQueryHasColumns(t *testing.T) {
|
||||
query := &plugin.Query{
|
||||
Cmd: metadata.CmdMany,
|
||||
Columns: []*plugin.Column{{}},
|
||||
}
|
||||
if putOutColumns(query) != true {
|
||||
t.Error("should be true when we have columns")
|
||||
}
|
||||
}
|
||||
102
internal/codegen/golang/sqlite_type.go
Normal file
102
internal/codegen/golang/sqlite_type.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/sdk"
|
||||
"github.com/sqlc-dev/sqlc/internal/debug"
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
func sqliteType(req *plugin.GenerateRequest, options *opts.Options, col *plugin.Column) string {
|
||||
dt := strings.ToLower(sdk.DataType(col.Type))
|
||||
notNull := col.NotNull || col.IsArray
|
||||
emitPointersForNull := options.EmitPointersForNullTypes
|
||||
|
||||
switch dt {
|
||||
|
||||
case "int", "integer", "tinyint", "smallint", "mediumint", "bigint", "unsignedbigint", "int2", "int8":
|
||||
if notNull {
|
||||
return "int64"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*int64"
|
||||
}
|
||||
return "sql.NullInt64"
|
||||
|
||||
case "blob":
|
||||
return "[]byte"
|
||||
|
||||
case "real", "double", "doubleprecision", "float":
|
||||
if notNull {
|
||||
return "float64"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*float64"
|
||||
}
|
||||
return "sql.NullFloat64"
|
||||
|
||||
case "boolean", "bool":
|
||||
if notNull {
|
||||
return "bool"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*bool"
|
||||
}
|
||||
return "sql.NullBool"
|
||||
|
||||
case "date", "datetime", "timestamp":
|
||||
if notNull {
|
||||
return "time.Time"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*time.Time"
|
||||
}
|
||||
return "sql.NullTime"
|
||||
|
||||
case "json", "jsonb":
|
||||
return "json.RawMessage"
|
||||
|
||||
case "any":
|
||||
return "any"
|
||||
|
||||
}
|
||||
|
||||
switch {
|
||||
|
||||
case strings.HasPrefix(dt, "character"),
|
||||
strings.HasPrefix(dt, "varchar"),
|
||||
strings.HasPrefix(dt, "varyingcharacter"),
|
||||
strings.HasPrefix(dt, "nchar"),
|
||||
strings.HasPrefix(dt, "nativecharacter"),
|
||||
strings.HasPrefix(dt, "nvarchar"),
|
||||
dt == "text",
|
||||
dt == "clob":
|
||||
if notNull {
|
||||
return "string"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*string"
|
||||
}
|
||||
return "sql.NullString"
|
||||
|
||||
case strings.HasPrefix(dt, "decimal"), dt == "numeric":
|
||||
if notNull {
|
||||
return "float64"
|
||||
}
|
||||
if emitPointersForNull {
|
||||
return "*float64"
|
||||
}
|
||||
return "sql.NullFloat64"
|
||||
|
||||
default:
|
||||
if debug.Active {
|
||||
log.Printf("unknown SQLite type: %s\n", dt)
|
||||
}
|
||||
|
||||
return "any"
|
||||
|
||||
}
|
||||
}
|
||||
53
internal/codegen/golang/struct.go
Normal file
53
internal/codegen/golang/struct.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package golang
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/sqlc-dev/sqlc/internal/codegen/golang/opts"
|
||||
"github.com/sqlc-dev/sqlc/internal/plugin"
|
||||
)
|
||||
|
||||
type Struct struct {
|
||||
Table *plugin.Identifier
|
||||
Name string
|
||||
Fields []Field
|
||||
Comment string
|
||||
// IsModel is true for table structs that live in the models file. When
|
||||
// the models file is generated into a different Go package, references
|
||||
// to these types from query files must be qualified.
|
||||
IsModel bool
|
||||
}
|
||||
|
||||
func StructName(name string, options *opts.Options) string {
|
||||
if rename := options.Rename[name]; rename != "" {
|
||||
return rename
|
||||
}
|
||||
var out strings.Builder
|
||||
name = strings.Map(func(r rune) rune {
|
||||
if unicode.IsLetter(r) {
|
||||
return r
|
||||
}
|
||||
if unicode.IsDigit(r) {
|
||||
return r
|
||||
}
|
||||
return rune('_')
|
||||
}, name)
|
||||
|
||||
for p := range strings.SplitSeq(name, "_") {
|
||||
if _, found := options.InitialismsMap[p]; found {
|
||||
out.WriteString(strings.ToUpper(p))
|
||||
} else {
|
||||
out.WriteString(strings.Title(p))
|
||||
}
|
||||
}
|
||||
|
||||
// If a name has a digit as its first char, prepand an underscore to make it a valid Go name.
|
||||
r, _ := utf8.DecodeRuneInString(out.String())
|
||||
if unicode.IsDigit(r) {
|
||||
return "_" + out.String()
|
||||
} else {
|
||||
return out.String()
|
||||
}
|
||||
}
|
||||
7
internal/codegen/golang/template.go
Normal file
7
internal/codegen/golang/template.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package golang
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed templates/*
|
||||
//go:embed templates/*/*
|
||||
var templates embed.FS
|
||||
@@ -0,0 +1,52 @@
|
||||
{{define "copyfromCodeGoSqlDriver"}}
|
||||
{{range .GoQueries}}
|
||||
{{if eq .Cmd ":copyfrom" }}
|
||||
var readerHandlerSequenceFor{{.MethodName}} uint32 = 1
|
||||
|
||||
func convertRowsFor{{.MethodName}}(w *io.PipeWriter, {{.Arg.SlicePair}}) {
|
||||
e := mysqltsv.NewEncoder(w, {{ len .Arg.CopyFromMySQLFields }}, nil)
|
||||
for _, row := range {{.Arg.Name}} {
|
||||
{{- with $arg := .Arg }}
|
||||
{{- range $arg.CopyFromMySQLFields}}
|
||||
{{- if eq .Type "string"}}
|
||||
e.AppendString({{if $arg.Struct}}row.{{.Name}}{{else}}row{{end}})
|
||||
{{- else if or (eq .Type "[]byte") (eq .Type "json.RawMessage")}}
|
||||
e.AppendBytes({{if $arg.Struct}}row.{{.Name}}{{else}}row{{end}})
|
||||
{{- else}}
|
||||
e.AppendValue({{if $arg.Struct}}row.{{.Name}}{{else}}row{{end}})
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
}
|
||||
w.CloseWithError(e.Close())
|
||||
}
|
||||
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
// {{.MethodName}} uses MySQL's LOAD DATA LOCAL INFILE and is not atomic.
|
||||
//
|
||||
// Errors and duplicate keys are treated as warnings and insertion will
|
||||
// continue, even without an error for some cases. Use this in a transaction
|
||||
// and use SHOW WARNINGS to check for any problems and roll back if you want to.
|
||||
//
|
||||
// Check the documentation for more information:
|
||||
// https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-error-handling
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context{{if $.EmitMethodsWithDBArgument}}, db DBTX{{end}}, {{.Arg.SlicePair}}) (int64, error) {
|
||||
pr, pw := io.Pipe()
|
||||
defer pr.Close()
|
||||
rh := fmt.Sprintf("{{.MethodName}}_%d", atomic.AddUint32(&readerHandlerSequenceFor{{.MethodName}}, 1))
|
||||
mysql.RegisterReaderHandler(rh, func() io.Reader { return pr })
|
||||
defer mysql.DeregisterReaderHandler(rh)
|
||||
go convertRowsFor{{.MethodName}}(pw, {{.Arg.Name}})
|
||||
// The string interpolation is necessary because LOAD DATA INFILE requires
|
||||
// the file name to be given as a literal string.
|
||||
result, err := {{if (not $.EmitMethodsWithDBArgument)}}q.{{end}}db.ExecContext(ctx, fmt.Sprintf("LOAD DATA LOCAL INFILE '%s' INTO TABLE {{.TableIdentifierForMySQL}} %s ({{range $index, $name := .Arg.ColumnNames}}{{if gt $index 0}}, {{end}}{{$name}}{{end}})", "Reader::" + rh, mysqltsv.Escaping))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
134
internal/codegen/golang/templates/pgx/batchCode.tmpl
Normal file
134
internal/codegen/golang/templates/pgx/batchCode.tmpl
Normal file
@@ -0,0 +1,134 @@
|
||||
{{define "batchCodePgx"}}
|
||||
|
||||
var (
|
||||
ErrBatchAlreadyClosed = errors.New("batch already closed")
|
||||
)
|
||||
|
||||
{{range .GoQueries}}
|
||||
{{if eq (hasPrefix .Cmd ":batch") true }}
|
||||
const {{.ConstantName}} = {{$.Q}}-- name: {{.MethodName}} {{.Cmd}}
|
||||
{{escape .SQL}}
|
||||
{{$.Q}}
|
||||
|
||||
type {{.MethodName}}BatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
{{if .Arg.Struct}}
|
||||
type {{.Arg.Type}} struct { {{- range .Arg.Struct.Fields}}
|
||||
{{.Name}} {{.Type}} {{if .Tag}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}}
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if .Ret.EmitStruct}}
|
||||
type {{.Ret.Type}} struct { {{- range .Ret.Struct.Fields}}
|
||||
{{.Name}} {{.Type}} {{if .Tag}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}}
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{ if $.EmitMethodsWithDBArgument}}db DBTX,{{end}} {{.Arg.SlicePair}}) *{{.MethodName}}BatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range {{index .Arg.Name}} {
|
||||
vals := []any{
|
||||
{{- if .Arg.Struct }}
|
||||
{{- range .Arg.Struct.Fields }}
|
||||
a.{{.Name}},
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
a,
|
||||
{{- end }}
|
||||
}
|
||||
batch.Queue({{.ConstantName}}, vals...)
|
||||
}
|
||||
br := {{if not $.EmitMethodsWithDBArgument}}q.{{end}}db.SendBatch(ctx, batch)
|
||||
return &{{.MethodName}}BatchResults{br,len({{.Arg.Name}}),false}
|
||||
}
|
||||
|
||||
{{if eq .Cmd ":batchexec"}}
|
||||
func (b *{{.MethodName}}BatchResults) Exec(f func(int, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := b.br.Exec()
|
||||
if f != nil {
|
||||
f(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":batchmany"}}
|
||||
func (b *{{.MethodName}}BatchResults) Query(f func(int, []{{.Ret.DefineType}}, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
{{- if $.EmitEmptySlices}}
|
||||
items := []{{.Ret.DefineType}}{}
|
||||
{{else}}
|
||||
var items []{{.Ret.DefineType}}
|
||||
{{end -}}
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, items, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
err := func() error {
|
||||
rows, err := b.br.Query()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var {{.Ret.Name}} {{.Ret.Type}}
|
||||
if err := rows.Scan({{.Ret.Scan}}); err != nil {
|
||||
return err
|
||||
}
|
||||
items = append(items, {{.Ret.ReturnName}})
|
||||
}
|
||||
return rows.Err()
|
||||
}()
|
||||
if f != nil {
|
||||
f(t, items, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":batchone"}}
|
||||
func (b *{{.MethodName}}BatchResults) QueryRow(f func(int, {{.Ret.DefineType}}, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
var {{.Ret.Name}} {{.Ret.Type}}
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, {{if .Ret.IsPointer}}nil{{else}}{{.Ret.Name}}{{end}}, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
row := b.br.QueryRow()
|
||||
err := row.Scan({{.Ret.Scan}})
|
||||
if f != nil {
|
||||
f(t, {{.Ret.ReturnName}}, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
func (b *{{.MethodName}}BatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
51
internal/codegen/golang/templates/pgx/copyfromCopy.tmpl
Normal file
51
internal/codegen/golang/templates/pgx/copyfromCopy.tmpl
Normal file
@@ -0,0 +1,51 @@
|
||||
{{define "copyfromCodePgx"}}
|
||||
{{range .GoQueries}}
|
||||
{{if eq .Cmd ":copyfrom" }}
|
||||
// iteratorFor{{.MethodName}} implements pgx.CopyFromSource.
|
||||
type iteratorFor{{.MethodName}} struct {
|
||||
rows []{{.Arg.DefineType}}
|
||||
skippedFirstNextCall bool
|
||||
}
|
||||
|
||||
func (r *iteratorFor{{.MethodName}}) Next() bool {
|
||||
if len(r.rows) == 0 {
|
||||
return false
|
||||
}
|
||||
if !r.skippedFirstNextCall {
|
||||
r.skippedFirstNextCall = true
|
||||
return true
|
||||
}
|
||||
r.rows = r.rows[1:]
|
||||
return len(r.rows) > 0
|
||||
}
|
||||
|
||||
func (r iteratorFor{{.MethodName}}) Values() ([]any, error) {
|
||||
return []any{
|
||||
{{- if .Arg.Struct }}
|
||||
{{- range .Arg.Struct.Fields }}
|
||||
r.rows[0].{{.Name}},
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
r.rows[0],
|
||||
{{- end }}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r iteratorFor{{.MethodName}}) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{- if $.EmitMethodsWithDBArgument -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.SlicePair}}) (int64, error) {
|
||||
return db.CopyFrom(ctx, {{.TableIdentifierAsGoSlice}}, {{.Arg.ColumnNamesAsGoSlice}}, &iteratorFor{{.MethodName}}{rows: {{.Arg.Name}}})
|
||||
{{- else -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{.Arg.SlicePair}}) (int64, error) {
|
||||
return q.db.CopyFrom(ctx, {{.TableIdentifierAsGoSlice}}, {{.Arg.ColumnNamesAsGoSlice}}, &iteratorFor{{.MethodName}}{rows: {{.Arg.Name}}})
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
37
internal/codegen/golang/templates/pgx/dbCode.tmpl
Normal file
37
internal/codegen/golang/templates/pgx/dbCode.tmpl
Normal file
@@ -0,0 +1,37 @@
|
||||
{{define "dbCodeTemplatePgx"}}
|
||||
|
||||
type DBTX interface {
|
||||
Exec(context.Context, string, ...any) (pgconn.CommandTag, error)
|
||||
Query(context.Context, string, ...any) (pgx.Rows, error)
|
||||
QueryRow(context.Context, string, ...any) pgx.Row
|
||||
{{- if .UsesCopyFrom }}
|
||||
CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error)
|
||||
{{- end }}
|
||||
{{- if .UsesBatch }}
|
||||
SendBatch(context.Context, *pgx.Batch) pgx.BatchResults
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
{{ if .EmitMethodsWithDBArgument}}
|
||||
func New() *Queries {
|
||||
return &Queries{}
|
||||
{{- else -}}
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
{{if not .EmitMethodsWithDBArgument}}
|
||||
db DBTX
|
||||
{{end}}
|
||||
}
|
||||
|
||||
{{if not .EmitMethodsWithDBArgument}}
|
||||
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
73
internal/codegen/golang/templates/pgx/interfaceCode.tmpl
Normal file
73
internal/codegen/golang/templates/pgx/interfaceCode.tmpl
Normal file
@@ -0,0 +1,73 @@
|
||||
{{define "interfaceCodePgx"}}
|
||||
type Querier interface {
|
||||
{{- $dbtxParam := .EmitMethodsWithDBArgument -}}
|
||||
{{- range .GoQueries}}
|
||||
{{- if and (eq .Cmd ":one") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) ({{.Ret.DefineType}}, error)
|
||||
{{- else if eq .Cmd ":one" }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ({{.Ret.DefineType}}, error)
|
||||
{{- end}}
|
||||
{{- if and (eq .Cmd ":many") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) ([]{{.Ret.DefineType}}, error)
|
||||
{{- else if eq .Cmd ":many" }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ([]{{.Ret.DefineType}}, error)
|
||||
{{- end}}
|
||||
{{- if and (eq .Cmd ":exec") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) error
|
||||
{{- else if eq .Cmd ":exec" }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) error
|
||||
{{- end}}
|
||||
{{- if and (eq .Cmd ":execrows") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) (int64, error)
|
||||
{{- else if eq .Cmd ":execrows" }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (int64, error)
|
||||
{{- end}}
|
||||
{{- if and (eq .Cmd ":execresult") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) (pgconn.CommandTag, error)
|
||||
{{- else if eq .Cmd ":execresult" }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (pgconn.CommandTag, error)
|
||||
{{- end}}
|
||||
{{- if and (eq .Cmd ":copyfrom") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.SlicePair}}) (int64, error)
|
||||
{{- else if eq .Cmd ":copyfrom" }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.SlicePair}}) (int64, error)
|
||||
{{- end}}
|
||||
{{- if and (or (eq .Cmd ":batchexec") (eq .Cmd ":batchmany") (eq .Cmd ":batchone")) ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.SlicePair}}) *{{.MethodName}}BatchResults
|
||||
{{- else if or (eq .Cmd ":batchexec") (eq .Cmd ":batchmany") (eq .Cmd ":batchone") }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.SlicePair}}) *{{.MethodName}}BatchResults
|
||||
{{- end}}
|
||||
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
var _ Querier = (*Queries)(nil)
|
||||
{{end}}
|
||||
142
internal/codegen/golang/templates/pgx/queryCode.tmpl
Normal file
142
internal/codegen/golang/templates/pgx/queryCode.tmpl
Normal file
@@ -0,0 +1,142 @@
|
||||
{{define "queryCodePgx"}}
|
||||
{{range .GoQueries}}
|
||||
{{if $.OutputQuery .SourceName}}
|
||||
{{if and (ne .Cmd ":copyfrom") (ne (hasPrefix .Cmd ":batch") true)}}
|
||||
const {{.ConstantName}} = {{$.Q}}-- name: {{.MethodName}} {{.Cmd}}
|
||||
{{escape .SQL}}
|
||||
{{$.Q}}
|
||||
{{end}}
|
||||
|
||||
{{if ne (hasPrefix .Cmd ":batch") true}}
|
||||
{{if .Arg.EmitStruct}}
|
||||
type {{.Arg.Type}} struct { {{- range .Arg.Struct.Fields}}
|
||||
{{.Name}} {{.Type}} {{if .Tag}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}}
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if .Ret.EmitStruct}}
|
||||
type {{.Ret.Type}} struct { {{- range .Ret.Struct.Fields}}
|
||||
{{.Name}} {{.Type}} {{if .Tag}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}}
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":one"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{- if $.EmitMethodsWithDBArgument -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) ({{.Ret.DefineType}}, error) {
|
||||
row := db.QueryRow(ctx, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- else -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ({{.Ret.DefineType}}, error) {
|
||||
row := q.db.QueryRow(ctx, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- end}}
|
||||
{{- if or (ne .Arg.Pair .Ret.Pair) (ne .Arg.DefineType .Ret.DefineType) }}
|
||||
var {{.Ret.Name}} {{.Ret.Type}}
|
||||
{{- end}}
|
||||
err := row.Scan({{.Ret.Scan}})
|
||||
{{- if $.WrapErrors}}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("query {{.MethodName}}: %w", err)
|
||||
}
|
||||
{{- end}}
|
||||
return {{.Ret.ReturnName}}, err
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":many"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{- if $.EmitMethodsWithDBArgument -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) ([]{{.Ret.DefineType}}, error) {
|
||||
rows, err := db.Query(ctx, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- else -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ([]{{.Ret.DefineType}}, error) {
|
||||
rows, err := q.db.Query(ctx, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- end}}
|
||||
if err != nil {
|
||||
return nil, {{if $.WrapErrors}}fmt.Errorf("query {{.MethodName}}: %w", err){{else}}err{{end}}
|
||||
}
|
||||
defer rows.Close()
|
||||
{{- if $.EmitEmptySlices}}
|
||||
items := []{{.Ret.DefineType}}{}
|
||||
{{else}}
|
||||
var items []{{.Ret.DefineType}}
|
||||
{{end -}}
|
||||
for rows.Next() {
|
||||
var {{.Ret.Name}} {{.Ret.Type}}
|
||||
if err := rows.Scan({{.Ret.Scan}}); err != nil {
|
||||
return nil, {{if $.WrapErrors}}fmt.Errorf("query {{.MethodName}}: %w", err){{else}}err{{end}}
|
||||
}
|
||||
items = append(items, {{.Ret.ReturnName}})
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, {{if $.WrapErrors}}fmt.Errorf("query {{.MethodName}}: %w", err){{else}}err{{end}}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":exec"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{- if $.EmitMethodsWithDBArgument -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) error {
|
||||
_, err := db.Exec(ctx, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- else -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) error {
|
||||
_, err := q.db.Exec(ctx, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- end}}
|
||||
{{- if $.WrapErrors }}
|
||||
if err != nil {
|
||||
return fmt.Errorf("query {{.MethodName}}: %w", err)
|
||||
}
|
||||
return nil
|
||||
{{- else }}
|
||||
return err
|
||||
{{- end }}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":execrows"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{if $.EmitMethodsWithDBArgument -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) (int64, error) {
|
||||
result, err := db.Exec(ctx, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- else -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- end}}
|
||||
if err != nil {
|
||||
return 0, {{if $.WrapErrors}}fmt.Errorf("query {{.MethodName}}: %w", err){{else}}err{{end}}
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":execresult"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{- if $.EmitMethodsWithDBArgument -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) (pgconn.CommandTag, error) {
|
||||
{{queryRetval .}} db.Exec(ctx, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- else -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (pgconn.CommandTag, error) {
|
||||
{{queryRetval .}} q.db.Exec(ctx, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- end}}
|
||||
{{- if $.WrapErrors}}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("query {{.MethodName}}: %w", err)
|
||||
}
|
||||
return result, err
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
105
internal/codegen/golang/templates/stdlib/dbCode.tmpl
Normal file
105
internal/codegen/golang/templates/stdlib/dbCode.tmpl
Normal file
@@ -0,0 +1,105 @@
|
||||
{{define "dbCodeTemplateStd"}}
|
||||
type DBTX interface {
|
||||
ExecContext(context.Context, string, ...any) (sql.Result, error)
|
||||
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
|
||||
QueryRowContext(context.Context, string, ...any) *sql.Row
|
||||
}
|
||||
|
||||
{{ if .EmitMethodsWithDBArgument}}
|
||||
func New() *Queries {
|
||||
return &Queries{}
|
||||
{{- else -}}
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
{{if .EmitPreparedQueries}}
|
||||
func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
|
||||
q := Queries{db: db}
|
||||
var err error
|
||||
{{- if eq (len .GoQueries) 0 }}
|
||||
_ = err
|
||||
{{- end }}
|
||||
{{- range .GoQueries }}
|
||||
if q.{{.FieldName}}, err = db.PrepareContext(ctx, {{.ConstantName}}); err != nil {
|
||||
return nil, fmt.Errorf("error preparing query {{.MethodName}}: %w", err)
|
||||
}
|
||||
{{- end}}
|
||||
return &q, nil
|
||||
}
|
||||
|
||||
func (q *Queries) Close() error {
|
||||
var err error
|
||||
{{- range .GoQueries }}
|
||||
if q.{{.FieldName}} != nil {
|
||||
if cerr := q.{{.FieldName}}.Close(); cerr != nil {
|
||||
err = fmt.Errorf("error closing {{.FieldName}}: %w", cerr)
|
||||
}
|
||||
}
|
||||
{{- end}}
|
||||
return err
|
||||
}
|
||||
|
||||
func (q *Queries) exec(ctx context.Context, stmt *sql.Stmt, query string, args ...any) (sql.Result, error) {
|
||||
switch {
|
||||
case stmt != nil && q.tx != nil:
|
||||
return q.tx.StmtContext(ctx, stmt).ExecContext(ctx, args...)
|
||||
case stmt != nil:
|
||||
return stmt.ExecContext(ctx, args...)
|
||||
default:
|
||||
return q.db.ExecContext(ctx, query, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Queries) query(ctx context.Context, stmt *sql.Stmt, query string, args ...any) (*sql.Rows, error) {
|
||||
switch {
|
||||
case stmt != nil && q.tx != nil:
|
||||
return q.tx.StmtContext(ctx, stmt).QueryContext(ctx, args...)
|
||||
case stmt != nil:
|
||||
return stmt.QueryContext(ctx, args...)
|
||||
default:
|
||||
return q.db.QueryContext(ctx, query, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, args ...any) (*sql.Row) {
|
||||
switch {
|
||||
case stmt != nil && q.tx != nil:
|
||||
return q.tx.StmtContext(ctx, stmt).QueryRowContext(ctx, args...)
|
||||
case stmt != nil:
|
||||
return stmt.QueryRowContext(ctx, args...)
|
||||
default:
|
||||
return q.db.QueryRowContext(ctx, query, args...)
|
||||
}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
type Queries struct {
|
||||
{{- if not .EmitMethodsWithDBArgument}}
|
||||
db DBTX
|
||||
{{- end}}
|
||||
|
||||
{{- if .EmitPreparedQueries}}
|
||||
tx *sql.Tx
|
||||
{{- range .GoQueries}}
|
||||
{{.FieldName}} *sql.Stmt
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
{{if not .EmitMethodsWithDBArgument}}
|
||||
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
{{- if .EmitPreparedQueries}}
|
||||
tx: tx,
|
||||
{{- range .GoQueries}}
|
||||
{{.FieldName}}: q.{{.FieldName}},
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
}
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
63
internal/codegen/golang/templates/stdlib/interfaceCode.tmpl
Normal file
63
internal/codegen/golang/templates/stdlib/interfaceCode.tmpl
Normal file
@@ -0,0 +1,63 @@
|
||||
{{define "interfaceCodeStd"}}
|
||||
type Querier interface {
|
||||
{{- $dbtxParam := .EmitMethodsWithDBArgument -}}
|
||||
{{- range .GoQueries}}
|
||||
{{- if and (eq .Cmd ":one") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) ({{.Ret.DefineType}}, error)
|
||||
{{- else if eq .Cmd ":one"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ({{.Ret.DefineType}}, error)
|
||||
{{- end}}
|
||||
{{- if and (eq .Cmd ":many") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) ([]{{.Ret.DefineType}}, error)
|
||||
{{- else if eq .Cmd ":many"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) ([]{{.Ret.DefineType}}, error)
|
||||
{{- end}}
|
||||
{{- if and (eq .Cmd ":exec") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) error
|
||||
{{- else if eq .Cmd ":exec"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) error
|
||||
{{- end}}
|
||||
{{- if and (eq .Cmd ":execrows") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) (int64, error)
|
||||
{{- else if eq .Cmd ":execrows"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (int64, error)
|
||||
{{- end}}
|
||||
{{- if and (eq .Cmd ":execlastid") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) (int64, error)
|
||||
{{- else if eq .Cmd ":execlastid"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (int64, error)
|
||||
{{- end}}
|
||||
{{- if and (eq .Cmd ":execresult") ($dbtxParam) }}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, db DBTX, {{.Arg.Pair}}) (sql.Result, error)
|
||||
{{- else if eq .Cmd ":execresult"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
{{.MethodName}}(ctx context.Context, {{.Arg.Pair}}) (sql.Result, error)
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
}
|
||||
|
||||
var _ Querier = (*Queries)(nil)
|
||||
{{end}}
|
||||
171
internal/codegen/golang/templates/stdlib/queryCode.tmpl
Normal file
171
internal/codegen/golang/templates/stdlib/queryCode.tmpl
Normal file
@@ -0,0 +1,171 @@
|
||||
{{define "queryCodeStd"}}
|
||||
{{range .GoQueries}}
|
||||
{{if $.OutputQuery .SourceName}}
|
||||
const {{.ConstantName}} = {{$.Q}}-- name: {{.MethodName}} {{.Cmd}}
|
||||
{{escape .SQL}}
|
||||
{{$.Q}}
|
||||
|
||||
{{if .Arg.EmitStruct}}
|
||||
type {{.Arg.Type}} struct { {{- range .Arg.UniqueFields}}
|
||||
{{.Name}} {{.Type}} {{if .Tag}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}}
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if .Ret.EmitStruct}}
|
||||
type {{.Ret.Type}} struct { {{- range .Ret.Struct.Fields}}
|
||||
{{.Name}} {{.Type}} {{if .Tag}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}}
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":one"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{ dbarg }} {{.Arg.Pair}}) ({{.Ret.DefineType}}, error) {
|
||||
{{- template "queryCodeStdExec" . }}
|
||||
{{- if or (ne .Arg.Pair .Ret.Pair) (ne .Arg.DefineType .Ret.DefineType) }}
|
||||
var {{.Ret.Name}} {{.Ret.Type}}
|
||||
{{- end}}
|
||||
err := row.Scan({{.Ret.Scan}})
|
||||
{{- if $.WrapErrors}}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("query {{.MethodName}}: %w", err)
|
||||
}
|
||||
{{- end}}
|
||||
return {{.Ret.ReturnName}}, err
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":many"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{ dbarg }} {{.Arg.Pair}}) ([]{{.Ret.DefineType}}, error) {
|
||||
{{- template "queryCodeStdExec" . }}
|
||||
if err != nil {
|
||||
return nil, {{if $.WrapErrors}}fmt.Errorf("query {{.MethodName}}: %w", err){{else}}err{{end}}
|
||||
}
|
||||
defer rows.Close()
|
||||
{{- if $.EmitEmptySlices}}
|
||||
items := []{{.Ret.DefineType}}{}
|
||||
{{else}}
|
||||
var items []{{.Ret.DefineType}}
|
||||
{{end -}}
|
||||
for rows.Next() {
|
||||
var {{.Ret.Name}} {{.Ret.Type}}
|
||||
if err := rows.Scan({{.Ret.Scan}}); err != nil {
|
||||
return nil, {{if $.WrapErrors}}fmt.Errorf("query {{.MethodName}}: %w", err){{else}}err{{end}}
|
||||
}
|
||||
items = append(items, {{.Ret.ReturnName}})
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, {{if $.WrapErrors}}fmt.Errorf("query {{.MethodName}}: %w", err){{else}}err{{end}}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, {{if $.WrapErrors}}fmt.Errorf("query {{.MethodName}}: %w", err){{else}}err{{end}}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":exec"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{ dbarg }} {{.Arg.Pair}}) error {
|
||||
{{- template "queryCodeStdExec" . }}
|
||||
{{- if $.WrapErrors}}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("query {{.MethodName}}: %w", err)
|
||||
}
|
||||
{{- end}}
|
||||
return err
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":execrows"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{ dbarg }} {{.Arg.Pair}}) (int64, error) {
|
||||
{{- template "queryCodeStdExec" . }}
|
||||
if err != nil {
|
||||
return 0, {{if $.WrapErrors}}fmt.Errorf("query {{.MethodName}}: %w", err){{else}}err{{end}}
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":execlastid"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{ dbarg }} {{.Arg.Pair}}) (int64, error) {
|
||||
{{- template "queryCodeStdExec" . }}
|
||||
if err != nil {
|
||||
return 0, {{if $.WrapErrors}}fmt.Errorf("query {{.MethodName}}: %w", err){{else}}err{{end}}
|
||||
}
|
||||
return result.LastInsertId()
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{if eq .Cmd ":execresult"}}
|
||||
{{range .Comments}}//{{.}}
|
||||
{{end -}}
|
||||
func (q *Queries) {{.MethodName}}(ctx context.Context, {{ dbarg }} {{.Arg.Pair}}) (sql.Result, error) {
|
||||
{{- template "queryCodeStdExec" . }}
|
||||
{{- if $.WrapErrors}}
|
||||
if err != nil {
|
||||
err = fmt.Errorf("query {{.MethodName}}: %w", err)
|
||||
}
|
||||
return result, err
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "queryCodeStdExec"}}
|
||||
{{- if .Arg.HasSqlcSlices }}
|
||||
query := {{.ConstantName}}
|
||||
var queryParams []any
|
||||
{{- if .Arg.Struct }}
|
||||
{{- $arg := .Arg }}
|
||||
{{- range .Arg.Struct.Fields }}
|
||||
{{- if .HasSqlcSlice }}
|
||||
if len({{$arg.VariableForField .}}) > 0 {
|
||||
for _, v := range {{$arg.VariableForField .}} {
|
||||
queryParams = append(queryParams, v)
|
||||
}
|
||||
query = strings.Replace(query, "/*SLICE:{{.Column.Name}}*/?", strings.Repeat(",?", len({{$arg.VariableForField .}}))[1:], 1)
|
||||
} else {
|
||||
query = strings.Replace(query, "/*SLICE:{{.Column.Name}}*/?", "NULL", 1)
|
||||
}
|
||||
{{- else }}
|
||||
queryParams = append(queryParams, {{$arg.VariableForField .}})
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
{{- /* Single argument parameter to this goroutine (they are not packed
|
||||
in a struct), because .Arg.HasSqlcSlices further up above was true,
|
||||
this section is 100% a slice (impossible to get here otherwise).
|
||||
*/}}
|
||||
if len({{.Arg.Name}}) > 0 {
|
||||
for _, v := range {{.Arg.Name}} {
|
||||
queryParams = append(queryParams, v)
|
||||
}
|
||||
query = strings.Replace(query, "/*SLICE:{{.Arg.Column.Name}}*/?", strings.Repeat(",?", len({{.Arg.Name}}))[1:], 1)
|
||||
} else {
|
||||
query = strings.Replace(query, "/*SLICE:{{.Arg.Column.Name}}*/?", "NULL", 1)
|
||||
}
|
||||
{{- end }}
|
||||
{{- if emitPreparedQueries }}
|
||||
{{ queryRetval . }} {{ queryMethod . }}(ctx, nil, query, queryParams...)
|
||||
{{- else}}
|
||||
{{ queryRetval . }} {{ queryMethod . }}(ctx, query, queryParams...)
|
||||
{{- end -}}
|
||||
{{- else if emitPreparedQueries }}
|
||||
{{- queryRetval . }} {{ queryMethod . }}(ctx, q.{{.FieldName}}, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- else}}
|
||||
{{- queryRetval . }} {{ queryMethod . }}(ctx, {{.ConstantName}}, {{.Arg.Params}})
|
||||
{{- end -}}
|
||||
{{end}}
|
||||
254
internal/codegen/golang/templates/template.tmpl
Normal file
254
internal/codegen/golang/templates/template.tmpl
Normal file
@@ -0,0 +1,254 @@
|
||||
{{define "dbFile"}}
|
||||
{{if .BuildTags}}
|
||||
//go:build {{.BuildTags}}
|
||||
|
||||
{{end}}// Code generated by sqlc. DO NOT EDIT.
|
||||
{{if not .OmitSqlcVersion}}// versions:
|
||||
// sqlc {{.SqlcVersion}}
|
||||
{{end}}
|
||||
|
||||
package {{.Package}}
|
||||
|
||||
{{ if hasImports .SourceName }}
|
||||
import (
|
||||
{{range imports .SourceName}}
|
||||
{{range .}}{{.}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{template "dbCode" . }}
|
||||
{{end}}
|
||||
|
||||
{{define "dbCode"}}
|
||||
|
||||
{{if .SQLDriver.IsPGX }}
|
||||
{{- template "dbCodeTemplatePgx" .}}
|
||||
{{else}}
|
||||
{{- template "dbCodeTemplateStd" .}}
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
||||
|
||||
{{define "interfaceFile"}}
|
||||
{{if .BuildTags}}
|
||||
//go:build {{.BuildTags}}
|
||||
|
||||
{{end}}// Code generated by sqlc. DO NOT EDIT.
|
||||
{{if not .OmitSqlcVersion}}// versions:
|
||||
// sqlc {{.SqlcVersion}}
|
||||
{{end}}
|
||||
|
||||
package {{.Package}}
|
||||
|
||||
{{ if hasImports .SourceName }}
|
||||
import (
|
||||
{{range imports .SourceName}}
|
||||
{{range .}}{{.}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{template "interfaceCode" . }}
|
||||
{{end}}
|
||||
|
||||
{{define "interfaceCode"}}
|
||||
{{if .SQLDriver.IsPGX }}
|
||||
{{- template "interfaceCodePgx" .}}
|
||||
{{else}}
|
||||
{{- template "interfaceCodeStd" .}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "modelsFile"}}
|
||||
{{if .BuildTags}}
|
||||
//go:build {{.BuildTags}}
|
||||
|
||||
{{end}}// Code generated by sqlc. DO NOT EDIT.
|
||||
{{if not .OmitSqlcVersion}}// versions:
|
||||
// sqlc {{.SqlcVersion}}
|
||||
{{end}}
|
||||
|
||||
package {{.ModelsPackage}}
|
||||
|
||||
{{ if hasImports .SourceName }}
|
||||
import (
|
||||
{{range imports .SourceName}}
|
||||
{{range .}}{{.}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{template "modelsCode" . }}
|
||||
{{end}}
|
||||
|
||||
{{define "modelsCode"}}
|
||||
{{range .Enums}}
|
||||
{{if .Comment}}{{comment .Comment}}{{end}}
|
||||
type {{.Name}} string
|
||||
|
||||
const (
|
||||
{{- range .Constants}}
|
||||
{{.Name}} {{.Type}} = "{{.Value}}"
|
||||
{{- end}}
|
||||
)
|
||||
|
||||
func (e *{{.Name}}) Scan(src any) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = {{.Name}}(s)
|
||||
case string:
|
||||
*e = {{.Name}}(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for {{.Name}}: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Null{{.Name}} struct {
|
||||
{{.Name}} {{.Name}} {{if .NameTag}}{{$.Q}}{{.NameTag}}{{$.Q}}{{end}}
|
||||
Valid bool {{if .ValidTag}}{{$.Q}}{{.ValidTag}}{{$.Q}}{{end}} // Valid is true if {{.Name}} is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *Null{{.Name}}) Scan(value any) error {
|
||||
if value == nil {
|
||||
ns.{{.Name}}, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.{{.Name}}.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns Null{{.Name}}) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.{{.Name}}), nil
|
||||
}
|
||||
|
||||
|
||||
{{ if $.EmitEnumValidMethod }}
|
||||
func (e {{.Name}}) Valid() bool {
|
||||
switch e {
|
||||
case {{ range $idx, $name := .Constants }}{{ if ne $idx 0 }},{{ "\n" }}{{ end }}{{ .Name }}{{ end }}:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{ if $.EmitAllEnumValues }}
|
||||
func All{{ .Name }}Values() []{{ .Name }} {
|
||||
return []{{ .Name }}{ {{ range .Constants}}{{ "\n" }}{{ .Name }},{{ end }}
|
||||
}
|
||||
}
|
||||
{{ end }}
|
||||
{{end}}
|
||||
|
||||
{{range .Structs}}
|
||||
{{if .Comment}}{{comment .Comment}}{{end}}
|
||||
type {{.Name}} struct { {{- range .Fields}}
|
||||
{{- if .Comment}}
|
||||
{{comment .Comment}}{{else}}
|
||||
{{- end}}
|
||||
{{.Name}} {{.Type}} {{if .Tag}}{{$.Q}}{{.Tag}}{{$.Q}}{{end}}
|
||||
{{- end}}
|
||||
}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "queryFile"}}
|
||||
{{if .BuildTags}}
|
||||
//go:build {{.BuildTags}}
|
||||
|
||||
{{end}}// Code generated by sqlc. DO NOT EDIT.
|
||||
{{if not .OmitSqlcVersion}}// versions:
|
||||
// sqlc {{.SqlcVersion}}
|
||||
{{end}}// source: {{.SourceName}}
|
||||
|
||||
package {{.Package}}
|
||||
|
||||
{{ if hasImports .SourceName }}
|
||||
import (
|
||||
{{range imports .SourceName}}
|
||||
{{range .}}{{.}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{template "queryCode" . }}
|
||||
{{end}}
|
||||
|
||||
{{define "queryCode"}}
|
||||
{{if .SQLDriver.IsPGX }}
|
||||
{{- template "queryCodePgx" .}}
|
||||
{{else}}
|
||||
{{- template "queryCodeStd" .}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "copyfromFile"}}
|
||||
{{if .BuildTags}}
|
||||
//go:build {{.BuildTags}}
|
||||
|
||||
{{end}}// Code generated by sqlc. DO NOT EDIT.
|
||||
{{if not .OmitSqlcVersion}}// versions:
|
||||
// sqlc {{.SqlcVersion}}
|
||||
{{end}}// source: {{.SourceName}}
|
||||
|
||||
package {{.Package}}
|
||||
|
||||
{{ if hasImports .SourceName }}
|
||||
import (
|
||||
{{range imports .SourceName}}
|
||||
{{range .}}{{.}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{template "copyfromCode" . }}
|
||||
{{end}}
|
||||
|
||||
{{define "copyfromCode"}}
|
||||
{{if .SQLDriver.IsPGX }}
|
||||
{{- template "copyfromCodePgx" .}}
|
||||
{{else if .SQLDriver.IsGoSQLDriverMySQL }}
|
||||
{{- template "copyfromCodeGoSqlDriver" .}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "batchFile"}}
|
||||
{{if .BuildTags}}
|
||||
//go:build {{.BuildTags}}
|
||||
|
||||
{{end}}// Code generated by sqlc. DO NOT EDIT.
|
||||
{{if not .OmitSqlcVersion}}// versions:
|
||||
// sqlc {{.SqlcVersion}}
|
||||
{{end}}// source: {{.SourceName}}
|
||||
|
||||
package {{.Package}}
|
||||
|
||||
{{ if hasImports .SourceName }}
|
||||
import (
|
||||
{{range imports .SourceName}}
|
||||
{{range .}}{{.}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
)
|
||||
{{end}}
|
||||
|
||||
{{template "batchCode" . }}
|
||||
{{end}}
|
||||
|
||||
{{define "batchCode"}}
|
||||
{{if .SQLDriver.IsPGX }}
|
||||
{{- template "batchCodePgx" .}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
Reference in New Issue
Block a user