From 0457b6011ccde2492559c857666f724271cfb7dc Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov Date: Fri, 29 Aug 2025 11:15:38 +0300 Subject: [PATCH 01/12] First ydb-go-sdk generation version. Added :queryrows comment to support .Query(...) syntax --- examples/authors/sqlc.yaml | 1 + examples/authors/ydb/db.go | 22 +- examples/authors/ydb/query.sql | 10 +- examples/authors/ydb/query.sql.go | 241 +++++++++--------- go.mod | 4 +- go.sum | 3 + internal/codegen/golang/driver.go | 2 + internal/codegen/golang/gen.go | 15 ++ internal/codegen/golang/imports.go | 30 ++- internal/codegen/golang/opts/enum.go | 10 + internal/codegen/golang/query.go | 46 +++- internal/codegen/golang/result.go | 1 + .../codegen/golang/templates/template.tmpl | 6 + .../golang/templates/ydb-go-sdk/dbCode.tmpl | 24 ++ .../templates/ydb-go-sdk/interfaceCode.tmpl | 45 ++++ .../templates/ydb-go-sdk/queryCode.tmpl | 217 ++++++++++++++++ internal/metadata/meta.go | 3 +- 17 files changed, 535 insertions(+), 145 deletions(-) create mode 100644 internal/codegen/golang/templates/ydb-go-sdk/dbCode.tmpl create mode 100644 internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl create mode 100644 internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl diff --git a/examples/authors/sqlc.yaml b/examples/authors/sqlc.yaml index 143cb608b0..49fe62ff76 100644 --- a/examples/authors/sqlc.yaml +++ b/examples/authors/sqlc.yaml @@ -52,6 +52,7 @@ sql: package: authors out: ydb emit_json_tags: true + sql_package: ydb-go-sdk rules: diff --git a/examples/authors/ydb/db.go b/examples/authors/ydb/db.go index e2b0a86b13..40762f0df3 100644 --- a/examples/authors/ydb/db.go +++ b/examples/authors/ydb/db.go @@ -6,26 +6,20 @@ package authors import ( "context" - "database/sql" + + "github.com/ydb-platform/ydb-go-sdk/v3/query" ) type DBTX interface { - ExecContext(context.Context, string, ...interface{}) (sql.Result, error) - PrepareContext(context.Context, string) (*sql.Stmt, error) - QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) - QueryRowContext(context.Context, string, ...interface{}) *sql.Row + Exec(ctx context.Context, sql string, opts ...query.ExecuteOption) error + Query(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Result, error) + QueryResultSet(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.ClosableResultSet, error) + QueryRow(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Row, error) } -func New(db DBTX) *Queries { - return &Queries{db: db} +func New() *Queries { + return &Queries{} } type Queries struct { - db DBTX -} - -func (q *Queries) WithTx(tx *sql.Tx) *Queries { - return &Queries{ - db: tx, - } } diff --git a/examples/authors/ydb/query.sql b/examples/authors/ydb/query.sql index bf672042c5..52bcd1112e 100644 --- a/examples/authors/ydb/query.sql +++ b/examples/authors/ydb/query.sql @@ -16,14 +16,8 @@ WHERE bio IS NULL; -- name: Count :one SELECT COUNT(*) FROM authors; --- name: COALESCE :many -SELECT id, name, COALESCE(bio, 'Null value!') FROM authors; - --- name: CreateOrUpdateAuthor :execresult -UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2); - --- name: CreateOrUpdateAuthorReturningBio :one -UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) RETURNING bio; +-- name: UpsertAuthor :queryrows +UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) RETURNING *; -- name: DeleteAuthor :exec DELETE FROM authors WHERE id = $p0; diff --git a/examples/authors/ydb/query.sql.go b/examples/authors/ydb/query.sql.go index 45d86c96fd..6826060aba 100644 --- a/examples/authors/ydb/query.sql.go +++ b/examples/authors/ydb/query.sql.go @@ -7,91 +7,43 @@ package authors import ( "context" - "database/sql" -) - -const cOALESCE = `-- name: COALESCE :many -SELECT id, name, COALESCE(bio, 'Null value!') FROM authors -` - -type COALESCERow struct { - ID uint64 `json:"id"` - Name string `json:"name"` - Bio string `json:"bio"` -} -func (q *Queries) COALESCE(ctx context.Context) ([]COALESCERow, error) { - rows, err := q.db.QueryContext(ctx, cOALESCE) - if err != nil { - return nil, err - } - defer rows.Close() - var items []COALESCERow - for rows.Next() { - var i COALESCERow - if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { - return nil, err - } - items = append(items, i) - } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err - } - return items, nil -} + "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/pkg/xerrors" + "github.com/ydb-platform/ydb-go-sdk/v3/query" +) const count = `-- name: Count :one SELECT COUNT(*) FROM authors ` -func (q *Queries) Count(ctx context.Context) (uint64, error) { - row := q.db.QueryRowContext(ctx, count) +func (q *Queries) Count(ctx context.Context, db DBTX, opts ...query.ExecuteOption) (uint64, error) { + row, err := db.QueryRow(ctx, count, opts...) var count uint64 - err := row.Scan(&count) - return count, err -} - -const createOrUpdateAuthor = `-- name: CreateOrUpdateAuthor :execresult -UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) -` - -type CreateOrUpdateAuthorParams struct { - P0 uint64 `json:"p0"` - P1 string `json:"p1"` - P2 *string `json:"p2"` -} - -func (q *Queries) CreateOrUpdateAuthor(ctx context.Context, arg CreateOrUpdateAuthorParams) (sql.Result, error) { - return q.db.ExecContext(ctx, createOrUpdateAuthor, arg.P0, arg.P1, arg.P2) -} - -const createOrUpdateAuthorReturningBio = `-- name: CreateOrUpdateAuthorReturningBio :one -UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) RETURNING bio -` - -type CreateOrUpdateAuthorReturningBioParams struct { - P0 uint64 `json:"p0"` - P1 string `json:"p1"` - P2 *string `json:"p2"` -} - -func (q *Queries) CreateOrUpdateAuthorReturningBio(ctx context.Context, arg CreateOrUpdateAuthorReturningBioParams) (*string, error) { - row := q.db.QueryRowContext(ctx, createOrUpdateAuthorReturningBio, arg.P0, arg.P1, arg.P2) - var bio *string - err := row.Scan(&bio) - return bio, err + if err != nil { + return count, xerrors.WithStackTrace(err) + } + err = row.Scan(&count) + if err != nil { + return count, xerrors.WithStackTrace(err) + } + return count, nil } const deleteAuthor = `-- name: DeleteAuthor :exec DELETE FROM authors WHERE id = $p0 ` -func (q *Queries) DeleteAuthor(ctx context.Context, p0 uint64) error { - _, err := q.db.ExecContext(ctx, deleteAuthor, p0) - return err +func (q *Queries) DeleteAuthor(ctx context.Context, db DBTX, p0 uint64, opts ...query.ExecuteOption) error { + err := db.Exec(ctx, deleteAuthor, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ + "$p0": p0, + })))..., + ) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil } const getAuthor = `-- name: GetAuthor :one @@ -99,11 +51,21 @@ SELECT id, name, bio FROM authors WHERE id = $p0 ` -func (q *Queries) GetAuthor(ctx context.Context, p0 uint64) (Author, error) { - row := q.db.QueryRowContext(ctx, getAuthor, p0) +func (q *Queries) GetAuthor(ctx context.Context, db DBTX, p0 uint64, opts ...query.ExecuteOption) (Author, error) { + row, err := db.QueryRow(ctx, getAuthor, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ + "$p0": p0, + })))..., + ) var i Author - err := row.Scan(&i.ID, &i.Name, &i.Bio) - return i, err + if err != nil { + return i, xerrors.WithStackTrace(err) + } + err = row.Scan(&i.ID, &i.Name, &i.Bio) + if err != nil { + return i, xerrors.WithStackTrace(err) + } + return i, nil } const getAuthorsByName = `-- name: GetAuthorsByName :many @@ -111,25 +73,25 @@ SELECT id, name, bio FROM authors WHERE name = $p0 ` -func (q *Queries) GetAuthorsByName(ctx context.Context, p0 string) ([]Author, error) { - rows, err := q.db.QueryContext(ctx, getAuthorsByName, p0) +func (q *Queries) GetAuthorsByName(ctx context.Context, db DBTX, p0 string, opts ...query.ExecuteOption) ([]Author, error) { + res, err := db.QueryResultSet(ctx, getAuthorsByName, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ + "$p0": p0, + })))..., + ) if err != nil { - return nil, err + return nil, xerrors.WithStackTrace(err) } - defer rows.Close() var items []Author - for rows.Next() { + for row := range res.Rows(ctx) { var i Author - if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { - return nil, err + if err := row.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, xerrors.WithStackTrace(err) } items = append(items, i) } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err + if err := res.Close(ctx); err != nil { + return nil, xerrors.WithStackTrace(err) } return items, nil } @@ -138,25 +100,21 @@ const listAuthors = `-- name: ListAuthors :many SELECT id, name, bio FROM authors ` -func (q *Queries) ListAuthors(ctx context.Context) ([]Author, error) { - rows, err := q.db.QueryContext(ctx, listAuthors) +func (q *Queries) ListAuthors(ctx context.Context, db DBTX, opts ...query.ExecuteOption) ([]Author, error) { + res, err := db.QueryResultSet(ctx, listAuthors, opts...) if err != nil { - return nil, err + return nil, xerrors.WithStackTrace(err) } - defer rows.Close() var items []Author - for rows.Next() { + for row := range res.Rows(ctx) { var i Author - if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { - return nil, err + if err := row.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, xerrors.WithStackTrace(err) } items = append(items, i) } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err + if err := res.Close(ctx); err != nil { + return nil, xerrors.WithStackTrace(err) } return items, nil } @@ -166,25 +124,21 @@ SELECT id, name, bio FROM authors WHERE bio IS NULL ` -func (q *Queries) ListAuthorsWithNullBio(ctx context.Context) ([]Author, error) { - rows, err := q.db.QueryContext(ctx, listAuthorsWithNullBio) +func (q *Queries) ListAuthorsWithNullBio(ctx context.Context, db DBTX, opts ...query.ExecuteOption) ([]Author, error) { + res, err := db.QueryResultSet(ctx, listAuthorsWithNullBio, opts...) if err != nil { - return nil, err + return nil, xerrors.WithStackTrace(err) } - defer rows.Close() var items []Author - for rows.Next() { + for row := range res.Rows(ctx) { var i Author - if err := rows.Scan(&i.ID, &i.Name, &i.Bio); err != nil { - return nil, err + if err := row.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, xerrors.WithStackTrace(err) } items = append(items, i) } - if err := rows.Close(); err != nil { - return nil, err - } - if err := rows.Err(); err != nil { - return nil, err + if err := res.Close(ctx); err != nil { + return nil, xerrors.WithStackTrace(err) } return items, nil } @@ -199,9 +153,64 @@ type UpdateAuthorByIDParams struct { P2 uint64 `json:"p2"` } -func (q *Queries) UpdateAuthorByID(ctx context.Context, arg UpdateAuthorByIDParams) (Author, error) { - row := q.db.QueryRowContext(ctx, updateAuthorByID, arg.P0, arg.P1, arg.P2) +func (q *Queries) UpdateAuthorByID(ctx context.Context, db DBTX, arg UpdateAuthorByIDParams, opts ...query.ExecuteOption) (Author, error) { + row, err := db.QueryRow(ctx, updateAuthorByID, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ + "$p0": arg.P0, + "$p1": arg.P1, + "$p2": arg.P2, + })))..., + ) var i Author - err := row.Scan(&i.ID, &i.Name, &i.Bio) - return i, err + if err != nil { + return i, xerrors.WithStackTrace(err) + } + err = row.Scan(&i.ID, &i.Name, &i.Bio) + if err != nil { + return i, xerrors.WithStackTrace(err) + } + return i, nil +} + +const upsertAuthor = `-- name: UpsertAuthor :queryrows +UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) RETURNING id, name, bio +` + +type UpsertAuthorParams struct { + P0 uint64 `json:"p0"` + P1 string `json:"p1"` + P2 *string `json:"p2"` +} + +func (q *Queries) UpsertAuthor(ctx context.Context, db DBTX, arg UpsertAuthorParams, opts ...query.ExecuteOption) ([]Author, error) { + result, err := db.Query(ctx, upsertAuthor, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ + "$p0": arg.P0, + "$p1": arg.P1, + "$p2": arg.P2, + })))..., + ) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var items []Author + for set, err := range result.ResultSets(ctx) { + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + for row, err := range set.Rows(ctx) { + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var i Author + if err := row.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, xerrors.WithStackTrace(err) + } + items = append(items, i) + } + } + if err := result.Close(ctx); err != nil { + return nil, xerrors.WithStackTrace(err) + } + return items, nil } diff --git a/go.mod b/go.mod index b5a03ba6c2..57a5640449 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/tetratelabs/wazero v1.9.0 github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 github.com/xeipuuv/gojsonschema v1.2.0 - github.com/ydb-platform/ydb-go-sdk/v3 v3.108.0 + github.com/ydb-platform/ydb-go-sdk/v3 v3.115.3 github.com/ydb-platform/yql-parsers v0.0.0-20250309001738-7d693911f333 golang.org/x/sync v0.13.0 google.golang.org/grpc v1.72.0 @@ -48,7 +48,7 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jonboulle/clockwork v0.3.0 // indirect + github.com/jonboulle/clockwork v0.5.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb // indirect diff --git a/go.sum b/go.sum index c5615aa00f..7a63df5fb7 100644 --- a/go.sum +++ b/go.sum @@ -146,6 +146,7 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -241,6 +242,8 @@ github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77 h1:LY github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= github.com/ydb-platform/ydb-go-sdk/v3 v3.108.0 h1:TwWSp3gRMcja/hRpOofncLvgxAXCmzpz5cGtmdaoITw= github.com/ydb-platform/ydb-go-sdk/v3 v3.108.0/go.mod h1:l5sSv153E18VvYcsmr51hok9Sjc16tEC8AXGbwrk+ho= +github.com/ydb-platform/ydb-go-sdk/v3 v3.115.3 h1:SFeSK2c+PmiToyNIhr143u+YDzLhl/kboXwKLYDk0O4= +github.com/ydb-platform/ydb-go-sdk/v3 v3.115.3/go.mod h1:Pp1w2xxUoLQ3NCNAwV7pvDq0TVQOdtAqs+ZiC+i8r14= github.com/ydb-platform/yql-parsers v0.0.0-20250309001738-7d693911f333 h1:KFtJwlPdOxWjCKXX0jFJ8k1FlbqbRbUW3k/kYSZX7SA= github.com/ydb-platform/yql-parsers v0.0.0-20250309001738-7d693911f333/go.mod h1:vrPJPS8cdPSV568YcXhB4bUwhyV8bmWKqmQ5c5Xi99o= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= diff --git a/internal/codegen/golang/driver.go b/internal/codegen/golang/driver.go index 5e3a533dcc..6e0596172f 100644 --- a/internal/codegen/golang/driver.go +++ b/internal/codegen/golang/driver.go @@ -8,6 +8,8 @@ func parseDriver(sqlPackage string) opts.SQLDriver { return opts.SQLDriverPGXV4 case opts.SQLPackagePGXV5: return opts.SQLDriverPGXV5 + case opts.SQLPackageYDBGoSDK: + return opts.SQLDriverYDBGoSDK default: return opts.SQLDriverLibPQ } diff --git a/internal/codegen/golang/gen.go b/internal/codegen/golang/gen.go index ac91cc537f..15b6952ba0 100644 --- a/internal/codegen/golang/gen.go +++ b/internal/codegen/golang/gen.go @@ -39,6 +39,7 @@ type tmplCtx struct { EmitAllEnumValues bool UsesCopyFrom bool UsesBatch bool + UsesQueryRows bool OmitSqlcVersion bool BuildTags string WrapErrors bool @@ -183,6 +184,7 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum, EmitAllEnumValues: options.EmitAllEnumValues, UsesCopyFrom: usesCopyFrom(queries), UsesBatch: usesBatch(queries), + UsesQueryRows: usesQueryRows(queries), SQLDriver: parseDriver(options.SqlPackage), Q: "`", Package: options.Package, @@ -209,6 +211,10 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum, return nil, errors.New(":batch* commands are only supported by pgx") } + if tctx.UsesQueryRows && tctx.SQLDriver != opts.SQLDriverYDBGoSDK { + return nil, errors.New(":queryrows commands are only supported by ydb-go-sdk") + } + funcMap := template.FuncMap{ "lowerTitle": sdk.LowerTitle, "comment": sdk.DoubleSlashComment, @@ -353,6 +359,15 @@ func usesBatch(queries []Query) bool { return false } +func usesQueryRows(queries []Query) bool { + for _, q := range queries { + if q.Cmd == metadata.CmdQueryRows { + return true + } + } + return false +} + func checkNoTimesForMySQLCopyFrom(queries []Query) error { for _, q := range queries { if q.Cmd != metadata.CmdCopyFrom { diff --git a/internal/codegen/golang/imports.go b/internal/codegen/golang/imports.go index ccca4f603c..17e426b8f9 100644 --- a/internal/codegen/golang/imports.go +++ b/internal/codegen/golang/imports.go @@ -132,6 +132,8 @@ func (i *importer) dbImports() fileImports { case opts.SQLDriverPGXV5: pkg = append(pkg, ImportSpec{Path: "github.com/jackc/pgx/v5/pgconn"}) pkg = append(pkg, ImportSpec{Path: "github.com/jackc/pgx/v5"}) + case opts.SQLDriverYDBGoSDK: + pkg = append(pkg, ImportSpec{Path: "github.com/ydb-platform/ydb-go-sdk/v3/query"}) default: std = append(std, ImportSpec{Path: "database/sql"}) if i.Options.EmitPreparedQueries { @@ -177,7 +179,9 @@ func buildImports(options *opts.Options, queries []Query, uses func(string) bool case opts.SQLDriverPGXV5: pkg[ImportSpec{Path: "github.com/jackc/pgx/v5/pgconn"}] = struct{}{} default: - std["database/sql"] = struct{}{} + if !sqlpkg.IsYDBGoSDK() { + std["database/sql"] = struct{}{} + } } } } @@ -267,6 +271,11 @@ func (i *importer) interfaceImports() fileImports { }) std["context"] = struct{}{} + + sqlpkg := parseDriver(i.Options.SqlPackage) + if sqlpkg.IsYDBGoSDK() { + pkg[ImportSpec{Path: "github.com/ydb-platform/ydb-go-sdk/v3/query"}] = struct{}{} + } return sortedImports(std, pkg) } @@ -395,13 +404,28 @@ func (i *importer) queryImports(filename string) fileImports { } sqlpkg := parseDriver(i.Options.SqlPackage) - if sqlcSliceScan() && !sqlpkg.IsPGX() { + if sqlcSliceScan() && !sqlpkg.IsPGX() && !sqlpkg.IsYDBGoSDK() { std["strings"] = struct{}{} } - if sliceScan() && !sqlpkg.IsPGX() { + if sliceScan() && !sqlpkg.IsPGX() && !sqlpkg.IsYDBGoSDK() { pkg[ImportSpec{Path: "github.com/lib/pq"}] = struct{}{} } + if sqlpkg.IsYDBGoSDK() { + hasParams := false + for _, q := range gq { + if !q.Arg.isEmpty() { + hasParams = true + break + } + } + if hasParams { + pkg[ImportSpec{Path: "github.com/ydb-platform/ydb-go-sdk/v3"}] = struct{}{} + } + pkg[ImportSpec{Path: "github.com/ydb-platform/ydb-go-sdk/v3/query"}] = struct{}{} + pkg[ImportSpec{Path: "github.com/ydb-platform/ydb-go-sdk/v3/pkg/xerrors"}] = struct{}{} + } + if i.Options.WrapErrors { std["fmt"] = struct{}{} } diff --git a/internal/codegen/golang/opts/enum.go b/internal/codegen/golang/opts/enum.go index 40457d040a..4d57a080a8 100644 --- a/internal/codegen/golang/opts/enum.go +++ b/internal/codegen/golang/opts/enum.go @@ -8,12 +8,14 @@ const ( SQLPackagePGXV4 string = "pgx/v4" SQLPackagePGXV5 string = "pgx/v5" SQLPackageStandard string = "database/sql" + SQLPackageYDBGoSDK string = "ydb-go-sdk" ) var validPackages = map[string]struct{}{ string(SQLPackagePGXV4): {}, string(SQLPackagePGXV5): {}, string(SQLPackageStandard): {}, + string(SQLPackageYDBGoSDK): {}, } func validatePackage(sqlPackage string) error { @@ -28,6 +30,7 @@ const ( SQLDriverPGXV5 = "github.com/jackc/pgx/v5" SQLDriverLibPQ = "github.com/lib/pq" SQLDriverGoSQLDriverMySQL = "github.com/go-sql-driver/mysql" + SQLDriverYDBGoSDK = "github.com/ydb-platform/ydb-go-sdk/v3" ) var validDrivers = map[string]struct{}{ @@ -35,6 +38,7 @@ var validDrivers = map[string]struct{}{ string(SQLDriverPGXV5): {}, string(SQLDriverLibPQ): {}, string(SQLDriverGoSQLDriverMySQL): {}, + string(SQLDriverYDBGoSDK): {}, } func validateDriver(sqlDriver string) error { @@ -52,12 +56,18 @@ func (d SQLDriver) IsGoSQLDriverMySQL() bool { return d == SQLDriverGoSQLDriverMySQL } +func (d SQLDriver) IsYDBGoSDK() bool { + return d == SQLDriverYDBGoSDK +} + func (d SQLDriver) Package() string { switch d { case SQLDriverPGXV4: return SQLPackagePGXV4 case SQLDriverPGXV5: return SQLPackagePGXV5 + case SQLDriverYDBGoSDK: + return SQLPackageYDBGoSDK default: return SQLPackageStandard } diff --git a/internal/codegen/golang/query.go b/internal/codegen/golang/query.go index 3b4fb2fa1a..eba27a7570 100644 --- a/internal/codegen/golang/query.go +++ b/internal/codegen/golang/query.go @@ -39,6 +39,10 @@ func (v QueryValue) isEmpty() bool { return v.Typ == "" && v.Name == "" && v.Struct == nil } +func (v QueryValue) IsEmpty() bool { + return v.isEmpty() +} + type Argument struct { Name string Type string @@ -254,6 +258,45 @@ func (v QueryValue) VariableForField(f Field) string { return v.Name + "." + f.Name } +func addDollarPrefix(name string) string { + if name == "" { + return name + } + if strings.HasPrefix(name, "$") { + return name + } + return "$" + name +} + +// YDBParamMapEntries returns entries for a map[string]any literal for YDB parameters. +func (v QueryValue) YDBParamMapEntries() string { + if v.isEmpty() { + return "" + } + var parts []string + if v.Struct == nil { + if v.Column != nil && v.Column.IsNamedParam { + name := v.Column.GetName() + if name != "" { + key := fmt.Sprintf("%q", addDollarPrefix(name)) + parts = append(parts, key+": "+escape(v.Name)) + } + } + } else { + for _, f := range v.Struct.Fields { + if f.Column != nil && f.Column.IsNamedParam { + name := f.Column.GetName() + if name != "" { + key := fmt.Sprintf("%q", addDollarPrefix(name)) + parts = append(parts, key+": "+escape(v.VariableForField(f))) + } + } + } + } + parts = append(parts, "") + return "\n" + strings.Join(parts, ",\n") +} + // A struct used to generate methods and fields on the Queries struct type Query struct { Cmd string @@ -271,7 +314,8 @@ type Query struct { func (q Query) hasRetType() bool { scanned := q.Cmd == metadata.CmdOne || q.Cmd == metadata.CmdMany || - q.Cmd == metadata.CmdBatchMany || q.Cmd == metadata.CmdBatchOne + q.Cmd == metadata.CmdBatchMany || q.Cmd == metadata.CmdBatchOne || + q.Cmd == metadata.CmdQueryRows return scanned && !q.Ret.isEmpty() } diff --git a/internal/codegen/golang/result.go b/internal/codegen/golang/result.go index 515d0a654f..8285f21b9e 100644 --- a/internal/codegen/golang/result.go +++ b/internal/codegen/golang/result.go @@ -336,6 +336,7 @@ var cmdReturnsData = map[string]struct{}{ metadata.CmdBatchOne: {}, metadata.CmdMany: {}, metadata.CmdOne: {}, + metadata.CmdQueryRows: {}, } func putOutColumns(query *plugin.Query) bool { diff --git a/internal/codegen/golang/templates/template.tmpl b/internal/codegen/golang/templates/template.tmpl index afd50c01ac..d06d4f41cc 100644 --- a/internal/codegen/golang/templates/template.tmpl +++ b/internal/codegen/golang/templates/template.tmpl @@ -25,6 +25,8 @@ import ( {{if .SQLDriver.IsPGX }} {{- template "dbCodeTemplatePgx" .}} +{{else if .SQLDriver.IsYDBGoSDK }} + {{- template "dbCodeYDB" .}} {{else}} {{- template "dbCodeTemplateStd" .}} {{end}} @@ -57,6 +59,8 @@ import ( {{define "interfaceCode"}} {{if .SQLDriver.IsPGX }} {{- template "interfaceCodePgx" .}} + {{else if .SQLDriver.IsYDBGoSDK }} + {{- template "interfaceCodeYDB" .}} {{else}} {{- template "interfaceCodeStd" .}} {{end}} @@ -188,6 +192,8 @@ import ( {{define "queryCode"}} {{if .SQLDriver.IsPGX }} {{- template "queryCodePgx" .}} +{{else if .SQLDriver.IsYDBGoSDK }} + {{- template "queryCodeYDB" .}} {{else}} {{- template "queryCodeStd" .}} {{end}} diff --git a/internal/codegen/golang/templates/ydb-go-sdk/dbCode.tmpl b/internal/codegen/golang/templates/ydb-go-sdk/dbCode.tmpl new file mode 100644 index 0000000000..706c17ae01 --- /dev/null +++ b/internal/codegen/golang/templates/ydb-go-sdk/dbCode.tmpl @@ -0,0 +1,24 @@ +{{define "dbCodeYDB"}} +type DBTX interface { + Exec(ctx context.Context, sql string, opts ...query.ExecuteOption) error + Query(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Result, error) + QueryResultSet(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.ClosableResultSet, error) + QueryRow(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Row, error) +} + +{{ 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}} +} + +{{end}} diff --git a/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl b/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl new file mode 100644 index 0000000000..3ce21b012f --- /dev/null +++ b/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl @@ -0,0 +1,45 @@ +{{define "interfaceCodeYDB"}} + type Querier interface { + {{- $dbtxParam := .EmitMethodsWithDBArgument -}} + {{- range .GoQueries}} + {{- if and (eq .Cmd ":one") ($dbtxParam) }} + {{range .Comments}}//{{.}} + {{end -}} + {{.MethodName}}(ctx context.Context, db DBTX, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ({{.Ret.DefineType}}, error) + {{- else if eq .Cmd ":one"}} + {{range .Comments}}//{{.}} + {{end -}} + {{.MethodName}}(ctx context.Context, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ({{.Ret.DefineType}}, error) + {{- end}} + {{- if and (eq .Cmd ":many") ($dbtxParam) }} + {{range .Comments}}//{{.}} + {{end -}} + {{.MethodName}}(ctx context.Context, db DBTX, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) + {{- else if eq .Cmd ":many"}} + {{range .Comments}}//{{.}} + {{end -}} + {{.MethodName}}(ctx context.Context, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) + {{- end}} + {{- if and (eq .Cmd ":exec") ($dbtxParam) }} + {{range .Comments}}//{{.}} + {{end -}} + {{.MethodName}}(ctx context.Context, db DBTX, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) error + {{- else if eq .Cmd ":exec"}} + {{range .Comments}}//{{.}} + {{end -}} + {{.MethodName}}(ctx context.Context, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) error + {{- end}} + {{- if and (eq .Cmd ":queryrows") ($dbtxParam) }} + {{range .Comments}}//{{.}} + {{end -}} + {{.MethodName}}(ctx context.Context, db DBTX, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]Author, error) + {{- else if eq .Cmd ":queryrows"}} + {{range .Comments}}//{{.}} + {{end -}} + {{.MethodName}}(ctx context.Context, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]Author, error) + {{- end}} + {{- end}} + } + + var _ Querier = (*Queries)(nil) +{{end}} diff --git a/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl b/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl new file mode 100644 index 0000000000..c9330d3722 --- /dev/null +++ b/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl @@ -0,0 +1,217 @@ +{{define "queryCodeYDB"}} +{{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, {{if $.EmitMethodsWithDBArgument}}db DBTX, {{end}}{{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ({{.Ret.DefineType}}, error) { + {{- $dbArg := "q.db" }}{{- if $.EmitMethodsWithDBArgument }}{{- $dbArg = "db" }}{{- end -}} + {{- if .Arg.IsEmpty }} + row, err := {{$dbArg}}.QueryRow(ctx, {{.ConstantName}}, opts...) + {{- else }} + row, err := {{$dbArg}}.QueryRow(ctx, {{.ConstantName}}, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ {{.Arg.YDBParamMapEntries}} })))..., + ) + {{- end }} + {{- if or (ne .Arg.Pair .Ret.Pair) (ne .Arg.DefineType .Ret.DefineType) }} + var {{.Ret.Name}} {{.Ret.Type}} + {{- end}} + if err != nil { + {{- if $.WrapErrors}} + return {{.Ret.ReturnName}}, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + {{- else }} + return {{.Ret.ReturnName}}, xerrors.WithStackTrace(err) + {{- end }} + } + err = row.Scan({{.Ret.Scan}}) + {{- if $.WrapErrors}} + if err != nil { + return {{.Ret.ReturnName}}, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + } + {{- else }} + if err != nil { + return {{.Ret.ReturnName}}, xerrors.WithStackTrace(err) + } + {{- end}} + return {{.Ret.ReturnName}}, nil +} +{{end}} + +{{if eq .Cmd ":many"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBArgument}}db DBTX, {{end}}{{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) { + {{- $dbArg := "q.db" }}{{- if $.EmitMethodsWithDBArgument }}{{- $dbArg = "db" }}{{- end -}} + {{- if .Arg.IsEmpty }} + res, err := {{$dbArg}}.QueryResultSet(ctx, {{.ConstantName}}, opts...) + {{- else }} + res, err := {{$dbArg}}.QueryResultSet(ctx, {{.ConstantName}}, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ {{.Arg.YDBParamMapEntries}} })))..., + ) + {{- end }} + if err != nil { + {{- if $.WrapErrors}} + return nil, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + {{- else }} + return nil, xerrors.WithStackTrace(err) + {{- end }} + } + {{- if $.EmitEmptySlices}} + items := []{{.Ret.DefineType}}{} + {{else}} + var items []{{.Ret.DefineType}} + {{end -}} + for row := range res.Rows(ctx) { + var {{.Ret.Name}} {{.Ret.Type}} + if err := row.Scan({{.Ret.Scan}}); err != nil { + {{- if $.WrapErrors}} + return nil, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + {{- else }} + return nil, xerrors.WithStackTrace(err) + {{- end }} + } + items = append(items, {{.Ret.ReturnName}}) + } + if err := res.Close(ctx); err != nil { + {{- if $.WrapErrors}} + return nil, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + {{- else }} + return nil, xerrors.WithStackTrace(err) + {{- end }} + } + return items, nil +} +{{end}} + +{{if eq .Cmd ":exec"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBArgument}}db DBTX, {{end}}{{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) error { + {{- $dbArg := "q.db" }}{{- if $.EmitMethodsWithDBArgument }}{{- $dbArg = "db" }}{{- end -}} + {{- if .Arg.IsEmpty }} + err := {{$dbArg}}.Exec(ctx, {{.ConstantName}}, opts...) + {{- else }} + err := {{$dbArg}}.Exec(ctx, {{.ConstantName}}, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ {{.Arg.YDBParamMapEntries}} })))..., + ) + {{- end }} + if err != nil { + {{- if $.WrapErrors }} + return xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + {{- else }} + return xerrors.WithStackTrace(err) + {{- end }} + } + return nil +} +{{end}} + +{{if eq .Cmd ":execresult"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBArgument}}db DBTX, {{end}}{{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) (query.Result, error) { + {{- $dbArg := "q.db" }}{{- if $.EmitMethodsWithDBArgument }}{{- $dbArg = "db" }}{{- end -}} + {{- if .Arg.IsEmpty }} + result, err := {{$dbArg}}.Query(ctx, {{.ConstantName}}, opts...) + {{- else }} + result, err := {{$dbArg}}.Query(ctx, {{.ConstantName}}, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ {{.Arg.YDBParamMapEntries}} })))..., + ) + {{- end }} + {{- if $.WrapErrors}} + if err != nil { + return result, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + } + {{- else }} + if err != nil { + return result, xerrors.WithStackTrace(err) + } + {{- end}} + return result, nil +} +{{end}} + +{{if eq .Cmd ":queryrows"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBArgument}}db DBTX, {{end}}{{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) { + {{- $dbArg := "q.db" }}{{- if $.EmitMethodsWithDBArgument }}{{- $dbArg = "db" }}{{- end -}} + {{- if .Arg.IsEmpty }} + result, err := {{$dbArg}}.Query(ctx, {{.ConstantName}}, opts...) + {{- else }} + result, err := {{$dbArg}}.Query(ctx, {{.ConstantName}}, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ {{.Arg.YDBParamMapEntries}} })))..., + ) + {{- end }} + if err != nil { + {{- if $.WrapErrors}} + return nil, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + {{- else }} + return nil, xerrors.WithStackTrace(err) + {{- end }} + } + {{- if $.EmitEmptySlices}} + items := []{{.Ret.DefineType}}{} + {{else}} + var items []{{.Ret.DefineType}} + {{end -}} + + for set, err := range result.ResultSets(ctx) { + if err != nil { + {{- if $.WrapErrors}} + return nil, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + {{- else }} + return nil, xerrors.WithStackTrace(err) + {{- end }} + } + for row, err := range set.Rows(ctx) { + if err != nil { + {{- if $.WrapErrors}} + return nil, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + {{- else }} + return nil, xerrors.WithStackTrace(err) + {{- end }} + } + var {{.Ret.Name}} {{.Ret.Type}} + if err := row.Scan({{.Ret.Scan}}); err != nil { + {{- if $.WrapErrors}} + return nil, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + {{- else }} + return nil, xerrors.WithStackTrace(err) + {{- end }} + } + items = append(items, i) + } + } + if err := result.Close(ctx); err != nil { + {{- if $.WrapErrors}} + return nil, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + {{- else }} + return nil, xerrors.WithStackTrace(err) + {{- end }} + } + return items, nil +} +{{end}} + +{{end}} +{{end}} +{{end}} diff --git a/internal/metadata/meta.go b/internal/metadata/meta.go index 8f63624d2c..c9a6657a8e 100644 --- a/internal/metadata/meta.go +++ b/internal/metadata/meta.go @@ -37,6 +37,7 @@ const ( CmdBatchExec = ":batchexec" CmdBatchMany = ":batchmany" CmdBatchOne = ":batchone" + CmdQueryRows = ":queryrows" ) // A query name must be a valid Go identifier @@ -106,7 +107,7 @@ func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string queryName := part[2] queryType := strings.TrimSpace(part[3]) switch queryType { - case CmdOne, CmdMany, CmdExec, CmdExecResult, CmdExecRows, CmdExecLastId, CmdCopyFrom, CmdBatchExec, CmdBatchMany, CmdBatchOne: + case CmdOne, CmdMany, CmdExec, CmdExecResult, CmdExecRows, CmdExecLastId, CmdCopyFrom, CmdBatchExec, CmdBatchMany, CmdBatchOne, CmdQueryRows: default: return "", "", fmt.Errorf("invalid query type: %s", queryType) } From 2b6c2c3c84ad0432bc4f58601b1ba2ef7b64ad6d Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov Date: Fri, 29 Aug 2025 13:25:20 +0300 Subject: [PATCH 02/12] Minor go.mod & go.sum fixes --- go.sum | 1 + 1 file changed, 1 insertion(+) diff --git a/go.sum b/go.sum index 7a63df5fb7..5f57a8acf9 100644 --- a/go.sum +++ b/go.sum @@ -146,6 +146,7 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jonboulle/clockwork v0.3.0 h1:9BSCMi8C+0qdApAp4auwX0RkLGUjs956h0EkuQymUhg= github.com/jonboulle/clockwork v0.3.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= From df80efa8745f2961f3ed2890b06bce5732381b30 Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov Date: Fri, 29 Aug 2025 13:31:44 +0300 Subject: [PATCH 03/12] Changed examples in examples/authors/ydb --- examples/authors/ydb/db.go | 5 +++-- examples/authors/ydb/query.sql.go | 32 +++++++++++++++---------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/examples/authors/ydb/db.go b/examples/authors/ydb/db.go index 40762f0df3..c3b16b4481 100644 --- a/examples/authors/ydb/db.go +++ b/examples/authors/ydb/db.go @@ -17,9 +17,10 @@ type DBTX interface { QueryRow(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Row, error) } -func New() *Queries { - return &Queries{} +func New(db DBTX) *Queries { + return &Queries{db: db} } type Queries struct { + db DBTX } diff --git a/examples/authors/ydb/query.sql.go b/examples/authors/ydb/query.sql.go index 6826060aba..ab7244edb1 100644 --- a/examples/authors/ydb/query.sql.go +++ b/examples/authors/ydb/query.sql.go @@ -17,8 +17,8 @@ const count = `-- name: Count :one SELECT COUNT(*) FROM authors ` -func (q *Queries) Count(ctx context.Context, db DBTX, opts ...query.ExecuteOption) (uint64, error) { - row, err := db.QueryRow(ctx, count, opts...) +func (q *Queries) Count(ctx context.Context, opts ...query.ExecuteOption) (uint64, error) { + row, err := q.db.QueryRow(ctx, count, opts...) var count uint64 if err != nil { return count, xerrors.WithStackTrace(err) @@ -34,8 +34,8 @@ const deleteAuthor = `-- name: DeleteAuthor :exec DELETE FROM authors WHERE id = $p0 ` -func (q *Queries) DeleteAuthor(ctx context.Context, db DBTX, p0 uint64, opts ...query.ExecuteOption) error { - err := db.Exec(ctx, deleteAuthor, +func (q *Queries) DeleteAuthor(ctx context.Context, p0 uint64, opts ...query.ExecuteOption) error { + err := q.db.Exec(ctx, deleteAuthor, append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ "$p0": p0, })))..., @@ -51,8 +51,8 @@ SELECT id, name, bio FROM authors WHERE id = $p0 ` -func (q *Queries) GetAuthor(ctx context.Context, db DBTX, p0 uint64, opts ...query.ExecuteOption) (Author, error) { - row, err := db.QueryRow(ctx, getAuthor, +func (q *Queries) GetAuthor(ctx context.Context, p0 uint64, opts ...query.ExecuteOption) (Author, error) { + row, err := q.db.QueryRow(ctx, getAuthor, append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ "$p0": p0, })))..., @@ -73,8 +73,8 @@ SELECT id, name, bio FROM authors WHERE name = $p0 ` -func (q *Queries) GetAuthorsByName(ctx context.Context, db DBTX, p0 string, opts ...query.ExecuteOption) ([]Author, error) { - res, err := db.QueryResultSet(ctx, getAuthorsByName, +func (q *Queries) GetAuthorsByName(ctx context.Context, p0 string, opts ...query.ExecuteOption) ([]Author, error) { + res, err := q.db.QueryResultSet(ctx, getAuthorsByName, append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ "$p0": p0, })))..., @@ -100,8 +100,8 @@ const listAuthors = `-- name: ListAuthors :many SELECT id, name, bio FROM authors ` -func (q *Queries) ListAuthors(ctx context.Context, db DBTX, opts ...query.ExecuteOption) ([]Author, error) { - res, err := db.QueryResultSet(ctx, listAuthors, opts...) +func (q *Queries) ListAuthors(ctx context.Context, opts ...query.ExecuteOption) ([]Author, error) { + res, err := q.db.QueryResultSet(ctx, listAuthors, opts...) if err != nil { return nil, xerrors.WithStackTrace(err) } @@ -124,8 +124,8 @@ SELECT id, name, bio FROM authors WHERE bio IS NULL ` -func (q *Queries) ListAuthorsWithNullBio(ctx context.Context, db DBTX, opts ...query.ExecuteOption) ([]Author, error) { - res, err := db.QueryResultSet(ctx, listAuthorsWithNullBio, opts...) +func (q *Queries) ListAuthorsWithNullBio(ctx context.Context, opts ...query.ExecuteOption) ([]Author, error) { + res, err := q.db.QueryResultSet(ctx, listAuthorsWithNullBio, opts...) if err != nil { return nil, xerrors.WithStackTrace(err) } @@ -153,8 +153,8 @@ type UpdateAuthorByIDParams struct { P2 uint64 `json:"p2"` } -func (q *Queries) UpdateAuthorByID(ctx context.Context, db DBTX, arg UpdateAuthorByIDParams, opts ...query.ExecuteOption) (Author, error) { - row, err := db.QueryRow(ctx, updateAuthorByID, +func (q *Queries) UpdateAuthorByID(ctx context.Context, arg UpdateAuthorByIDParams, opts ...query.ExecuteOption) (Author, error) { + row, err := q.db.QueryRow(ctx, updateAuthorByID, append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ "$p0": arg.P0, "$p1": arg.P1, @@ -182,8 +182,8 @@ type UpsertAuthorParams struct { P2 *string `json:"p2"` } -func (q *Queries) UpsertAuthor(ctx context.Context, db DBTX, arg UpsertAuthorParams, opts ...query.ExecuteOption) ([]Author, error) { - result, err := db.Query(ctx, upsertAuthor, +func (q *Queries) UpsertAuthor(ctx context.Context, arg UpsertAuthorParams, opts ...query.ExecuteOption) ([]Author, error) { + result, err := q.db.Query(ctx, upsertAuthor, append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ "$p0": arg.P0, "$p1": arg.P1, From eb4e5183cf117e8131867c3af441141130a325ab Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov Date: Fri, 29 Aug 2025 13:57:51 +0300 Subject: [PATCH 04/12] Rewrote examples test logic + local.YDB func to work with native driver instead of database/sql --- examples/authors/ydb/db_test.go | 25 ++++--- examples/authors/ydb/query.sql | 14 +++- examples/authors/ydb/query.sql.go | 117 ++++++++++++++++++++++-------- internal/sqltest/local/ydb.go | 49 +++++-------- 4 files changed, 129 insertions(+), 76 deletions(-) diff --git a/examples/authors/ydb/db_test.go b/examples/authors/ydb/db_test.go index 76b37306ef..eeb9a6e97e 100644 --- a/examples/authors/ydb/db_test.go +++ b/examples/authors/ydb/db_test.go @@ -6,6 +6,7 @@ import ( "github.com/sqlc-dev/sqlc/internal/sqltest/local" _ "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/query" ) func ptr(s string) *string { @@ -15,10 +16,10 @@ func ptr(s string) *string { func TestAuthors(t *testing.T) { ctx := context.Background() - test := local.YDB(t, []string{"schema.sql"}) - defer test.DB.Close() + db := local.YDB(t, []string{"schema.sql"}) + defer db.Close(ctx) - q := New(test.DB) + q := New(db.Query()) t.Run("InsertAuthors", func(t *testing.T) { authorsToInsert := []CreateOrUpdateAuthorParams{ @@ -38,7 +39,7 @@ func TestAuthors(t *testing.T) { } for _, author := range authorsToInsert { - if _, err := q.CreateOrUpdateAuthor(ctx, author); err != nil { + if err := q.CreateOrUpdateAuthor(ctx, author, query.WithIdempotent()); err != nil { t.Fatalf("failed to insert author %q: %v", author.P1, err) } } @@ -52,7 +53,7 @@ func TestAuthors(t *testing.T) { P2: &newBio, } - returnedBio, err := q.CreateOrUpdateAuthorReturningBio(ctx, arg) + returnedBio, err := q.CreateOrUpdateAuthorReturningBio(ctx, arg, query.WithIdempotent()) if err != nil { t.Fatalf("failed to create or update author: %v", err) } @@ -74,15 +75,17 @@ func TestAuthors(t *testing.T) { P2: 10, } - singleAuthor, err := q.UpdateAuthorByID(ctx, arg) + authors, err := q.UpdateAuthorByID(ctx, arg, query.WithIdempotent(), query.WithIdempotent()) if err != nil { t.Fatal(err) } - bio := "Null" - if singleAuthor.Bio != nil { - bio = *singleAuthor.Bio + for _, a := range authors { + bio := "Null" + if a.Bio != nil { + bio = *a.Bio + } + t.Logf("- ID: %d | Name: %s | Bio: %s", a.ID, a.Name, bio) } - t.Logf("- ID: %d | Name: %s | Bio: %s", singleAuthor.ID, singleAuthor.Name, bio) }) t.Run("ListAuthors", func(t *testing.T) { @@ -154,7 +157,7 @@ func TestAuthors(t *testing.T) { t.Run("Delete All Authors", func(t *testing.T) { var i uint64 for i = 1; i <= 13; i++ { - if err := q.DeleteAuthor(ctx, i); err != nil { + if err := q.DeleteAuthor(ctx, i, query.WithIdempotent()); err != nil { t.Fatalf("failed to delete authors: %v", err) } } diff --git a/examples/authors/ydb/query.sql b/examples/authors/ydb/query.sql index 52bcd1112e..e54b05ae0d 100644 --- a/examples/authors/ydb/query.sql +++ b/examples/authors/ydb/query.sql @@ -16,11 +16,17 @@ WHERE bio IS NULL; -- name: Count :one SELECT COUNT(*) FROM authors; --- name: UpsertAuthor :queryrows -UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) RETURNING *; +-- name: Coalesce :many +SELECT id, name, COALESCE(bio, 'Null value!') FROM authors; + +-- name: CreateOrUpdateAuthor :exec +UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2); + +-- name: CreateOrUpdateAuthorReturningBio :one +UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) RETURNING bio; -- name: DeleteAuthor :exec DELETE FROM authors WHERE id = $p0; --- name: UpdateAuthorByID :one -UPDATE authors SET name = $p0, bio = $p1 WHERE id = $p2 RETURNING *; +-- name: UpdateAuthorByID :queryrows +UPDATE authors SET name = $p0, bio = $p1 WHERE id = $p2 RETURNING *; \ No newline at end of file diff --git a/examples/authors/ydb/query.sql.go b/examples/authors/ydb/query.sql.go index ab7244edb1..7c9511e7ec 100644 --- a/examples/authors/ydb/query.sql.go +++ b/examples/authors/ydb/query.sql.go @@ -13,6 +13,35 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/query" ) +const coalesce = `-- name: Coalesce :many +SELECT id, name, COALESCE(bio, 'Null value!') FROM authors +` + +type CoalesceRow struct { + ID uint64 `json:"id"` + Name string `json:"name"` + Bio string `json:"bio"` +} + +func (q *Queries) Coalesce(ctx context.Context, opts ...query.ExecuteOption) ([]CoalesceRow, error) { + res, err := q.db.QueryResultSet(ctx, coalesce, opts...) + if err != nil { + return nil, xerrors.WithStackTrace(err) + } + var items []CoalesceRow + for row := range res.Rows(ctx) { + var i CoalesceRow + if err := row.Scan(&i.ID, &i.Name, &i.Bio); err != nil { + return nil, xerrors.WithStackTrace(err) + } + items = append(items, i) + } + if err := res.Close(ctx); err != nil { + return nil, xerrors.WithStackTrace(err) + } + return items, nil +} + const count = `-- name: Count :one SELECT COUNT(*) FROM authors ` @@ -30,6 +59,59 @@ func (q *Queries) Count(ctx context.Context, opts ...query.ExecuteOption) (uint6 return count, nil } +const createOrUpdateAuthor = `-- name: CreateOrUpdateAuthor :exec +UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) +` + +type CreateOrUpdateAuthorParams struct { + P0 uint64 `json:"p0"` + P1 string `json:"p1"` + P2 *string `json:"p2"` +} + +func (q *Queries) CreateOrUpdateAuthor(ctx context.Context, arg CreateOrUpdateAuthorParams, opts ...query.ExecuteOption) error { + err := q.db.Exec(ctx, createOrUpdateAuthor, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ + "$p0": arg.P0, + "$p1": arg.P1, + "$p2": arg.P2, + })))..., + ) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil +} + +const createOrUpdateAuthorReturningBio = `-- name: CreateOrUpdateAuthorReturningBio :one +UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) RETURNING bio +` + +type CreateOrUpdateAuthorReturningBioParams struct { + P0 uint64 `json:"p0"` + P1 string `json:"p1"` + P2 *string `json:"p2"` +} + +func (q *Queries) CreateOrUpdateAuthorReturningBio(ctx context.Context, arg CreateOrUpdateAuthorReturningBioParams, opts ...query.ExecuteOption) (*string, error) { + row, err := q.db.QueryRow(ctx, createOrUpdateAuthorReturningBio, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ + "$p0": arg.P0, + "$p1": arg.P1, + "$p2": arg.P2, + })))..., + ) + var bio *string + if err != nil { + return bio, xerrors.WithStackTrace(err) + } + err = row.Scan(&bio) + if err != nil { + return bio, xerrors.WithStackTrace(err) + } + return bio, nil +} + const deleteAuthor = `-- name: DeleteAuthor :exec DELETE FROM authors WHERE id = $p0 ` @@ -143,7 +225,7 @@ func (q *Queries) ListAuthorsWithNullBio(ctx context.Context, opts ...query.Exec return items, nil } -const updateAuthorByID = `-- name: UpdateAuthorByID :one +const updateAuthorByID = `-- name: UpdateAuthorByID :queryrows UPDATE authors SET name = $p0, bio = $p1 WHERE id = $p2 RETURNING id, name, bio ` @@ -153,37 +235,8 @@ type UpdateAuthorByIDParams struct { P2 uint64 `json:"p2"` } -func (q *Queries) UpdateAuthorByID(ctx context.Context, arg UpdateAuthorByIDParams, opts ...query.ExecuteOption) (Author, error) { - row, err := q.db.QueryRow(ctx, updateAuthorByID, - append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ - "$p0": arg.P0, - "$p1": arg.P1, - "$p2": arg.P2, - })))..., - ) - var i Author - if err != nil { - return i, xerrors.WithStackTrace(err) - } - err = row.Scan(&i.ID, &i.Name, &i.Bio) - if err != nil { - return i, xerrors.WithStackTrace(err) - } - return i, nil -} - -const upsertAuthor = `-- name: UpsertAuthor :queryrows -UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) RETURNING id, name, bio -` - -type UpsertAuthorParams struct { - P0 uint64 `json:"p0"` - P1 string `json:"p1"` - P2 *string `json:"p2"` -} - -func (q *Queries) UpsertAuthor(ctx context.Context, arg UpsertAuthorParams, opts ...query.ExecuteOption) ([]Author, error) { - result, err := q.db.Query(ctx, upsertAuthor, +func (q *Queries) UpdateAuthorByID(ctx context.Context, arg UpdateAuthorByIDParams, opts ...query.ExecuteOption) ([]Author, error) { + result, err := q.db.Query(ctx, updateAuthorByID, append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ "$p0": arg.P0, "$p1": arg.P1, diff --git a/internal/sqltest/local/ydb.go b/internal/sqltest/local/ydb.go index 8703b170b5..3505d84059 100644 --- a/internal/sqltest/local/ydb.go +++ b/internal/sqltest/local/ydb.go @@ -2,7 +2,6 @@ package local import ( "context" - "database/sql" "fmt" "hash/fnv" "math/rand" @@ -14,30 +13,25 @@ import ( migrate "github.com/sqlc-dev/sqlc/internal/migrations" "github.com/sqlc-dev/sqlc/internal/sql/sqlpath" "github.com/ydb-platform/ydb-go-sdk/v3" + "github.com/ydb-platform/ydb-go-sdk/v3/query" + "github.com/ydb-platform/ydb-go-sdk/v3/sugar" ) func init() { rand.Seed(time.Now().UnixNano()) } -func YDB(t *testing.T, migrations []string) TestYDB { +func YDB(t *testing.T, migrations []string) *ydb.Driver { return link_YDB(t, migrations, true) } -func ReadOnlyYDB(t *testing.T, migrations []string) TestYDB { +func ReadOnlyYDB(t *testing.T, migrations []string) *ydb.Driver { return link_YDB(t, migrations, false) } -type TestYDB struct { - DB *sql.DB - Prefix string -} - -func link_YDB(t *testing.T, migrations []string, rw bool) TestYDB { +func link_YDB(t *testing.T, migrations []string, rw bool) *ydb.Driver { t.Helper() - time.Sleep(1 * time.Second) // wait for YDB to start - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() @@ -72,16 +66,15 @@ func link_YDB(t *testing.T, migrations []string, rw bool) TestYDB { var name string if rw { - // name = fmt.Sprintf("sqlc_test_%s", id()) name = fmt.Sprintf("sqlc_test_%s", "test_new") } else { name = fmt.Sprintf("sqlc_test_%x", h.Sum(nil)) } - prefix := fmt.Sprintf("%s/%s", baseDB, name) - rootDSN := fmt.Sprintf("grpc://%s?database=%s", dbuiri, baseDB) - t.Logf("→ Opening root driver: %s", rootDSN) - driver, err := ydb.Open(ctx, rootDSN, + connectionString := fmt.Sprintf("grpc://%s%s", dbuiri, baseDB) + t.Logf("→ Opening YDB connection: %s", connectionString) + + db, err := ydb.Open(ctx, connectionString, ydb.WithInsecure(), ydb.WithDiscoveryInterval(time.Hour), ydb.WithNodeAddressMutator(func(_ string) string { @@ -89,29 +82,27 @@ func link_YDB(t *testing.T, migrations []string, rw bool) TestYDB { }), ) if err != nil { - t.Fatalf("failed to open root YDB connection: %s", err) + t.Fatalf("failed to open YDB connection: %s", err) } - connector, err := ydb.Connector( - driver, - ydb.WithTablePathPrefix(prefix), - ydb.WithAutoDeclare(), - ydb.WithNumericArgs(), - ) + prefix := fmt.Sprintf("%s/%s", baseDB, name) + t.Logf("→ Using prefix: %s", prefix) + + err = sugar.RemoveRecursive(ctx, db, prefix) if err != nil { - t.Fatalf("failed to create connector: %s", err) + t.Logf("Warning: failed to remove old data: %s", err) } - db := sql.OpenDB(connector) - t.Log("→ Applying migrations to prefix: ", prefix) - schemeCtx := ydb.WithQueryMode(ctx, ydb.SchemeQueryMode) for _, stmt := range seed { - _, err := db.ExecContext(schemeCtx, stmt) + err := db.Query().Exec(ctx, stmt, + query.WithTxControl(query.EmptyTxControl()), + ) if err != nil { t.Fatalf("failed to apply migration: %s\nSQL: %s", err, stmt) } } - return TestYDB{DB: db, Prefix: prefix} + + return db } From cde0fb6e69735ee73f5c930ede097e38c0e376c5 Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov <150552906+1NepuNep1@users.noreply.github.com> Date: Fri, 29 Aug 2025 19:03:24 +0300 Subject: [PATCH 05/12] Update internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl b/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl index c9330d3722..be8eff1094 100644 --- a/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl +++ b/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl @@ -198,7 +198,7 @@ func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBA return nil, xerrors.WithStackTrace(err) {{- end }} } - items = append(items, i) + items = append(items, {{.Ret.ReturnName}}) } } if err := result.Close(ctx); err != nil { From 8a838261140b03639e9725c48cc812373001ef55 Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov <150552906+1NepuNep1@users.noreply.github.com> Date: Fri, 29 Aug 2025 19:04:50 +0300 Subject: [PATCH 06/12] Update internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl b/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl index 3ce21b012f..2aafd6b81a 100644 --- a/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl +++ b/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl @@ -32,11 +32,11 @@ {{- if and (eq .Cmd ":queryrows") ($dbtxParam) }} {{range .Comments}}//{{.}} {{end -}} - {{.MethodName}}(ctx context.Context, db DBTX, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]Author, error) + {{.MethodName}}(ctx context.Context, db DBTX, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) {{- else if eq .Cmd ":queryrows"}} {{range .Comments}}//{{.}} {{end -}} - {{.MethodName}}(ctx context.Context, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]Author, error) + {{.MethodName}}(ctx context.Context, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) {{- end}} {{- end}} } From d58c59225e80d8bdb5583db2ca34a526f1898dae Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov Date: Fri, 29 Aug 2025 20:03:45 +0300 Subject: [PATCH 07/12] Minor style & logic fixes --- docker-compose.yml | 2 + internal/codegen/golang/query.go | 39 ++++++++++++------- .../codegen/golang/templates/template.tmpl | 2 +- .../golang/templates/ydb-go-sdk/dbCode.tmpl | 2 +- internal/config/v_two.json | 3 +- internal/sqltest/local/ydb.go | 8 ---- 6 files changed, 31 insertions(+), 25 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index c7d9c50db0..a751603965 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,8 +27,10 @@ services: - "2136:2136" - "8765:8765" restart: always + hostname: localhost environment: - YDB_USE_IN_MEMORY_PDISKS=true - GRPC_TLS_PORT=2135 - GRPC_PORT=2136 - MON_PORT=8765 + diff --git a/internal/codegen/golang/query.go b/internal/codegen/golang/query.go index eba27a7570..a299c95c87 100644 --- a/internal/codegen/golang/query.go +++ b/internal/codegen/golang/query.go @@ -273,30 +273,41 @@ func (v QueryValue) YDBParamMapEntries() string { if v.isEmpty() { return "" } + var parts []string - if v.Struct == nil { - if v.Column != nil && v.Column.IsNamedParam { - name := v.Column.GetName() + for _, field := range v.getParameterFields() { + if field.Column != nil && field.Column.IsNamedParam { + name := field.Column.GetName() if name != "" { key := fmt.Sprintf("%q", addDollarPrefix(name)) - parts = append(parts, key+": "+escape(v.Name)) - } - } - } else { - for _, f := range v.Struct.Fields { - if f.Column != nil && f.Column.IsNamedParam { - name := f.Column.GetName() - if name != "" { - key := fmt.Sprintf("%q", addDollarPrefix(name)) - parts = append(parts, key+": "+escape(v.VariableForField(f))) - } + variable := v.VariableForField(field) + parts = append(parts, key+": "+escape(variable)) } } } + + if len(parts) == 0 { + return "" + } + parts = append(parts, "") return "\n" + strings.Join(parts, ",\n") } +func (v QueryValue) getParameterFields() []Field { + if v.Struct == nil { + return []Field{ + { + Name: v.Name, + DBName: v.DBName, + Type: v.Typ, + Column: v.Column, + }, + } + } + return v.Struct.Fields +} + // A struct used to generate methods and fields on the Queries struct type Query struct { Cmd string diff --git a/internal/codegen/golang/templates/template.tmpl b/internal/codegen/golang/templates/template.tmpl index d06d4f41cc..f74b796349 100644 --- a/internal/codegen/golang/templates/template.tmpl +++ b/internal/codegen/golang/templates/template.tmpl @@ -26,7 +26,7 @@ import ( {{if .SQLDriver.IsPGX }} {{- template "dbCodeTemplatePgx" .}} {{else if .SQLDriver.IsYDBGoSDK }} - {{- template "dbCodeYDB" .}} + {{- template "dbCodeTemplateYDB" .}} {{else}} {{- template "dbCodeTemplateStd" .}} {{end}} diff --git a/internal/codegen/golang/templates/ydb-go-sdk/dbCode.tmpl b/internal/codegen/golang/templates/ydb-go-sdk/dbCode.tmpl index 706c17ae01..f79831d2e2 100644 --- a/internal/codegen/golang/templates/ydb-go-sdk/dbCode.tmpl +++ b/internal/codegen/golang/templates/ydb-go-sdk/dbCode.tmpl @@ -1,4 +1,4 @@ -{{define "dbCodeYDB"}} +{{define "dbCodeTemplateYDB"}} type DBTX interface { Exec(ctx context.Context, sql string, opts ...query.ExecuteOption) error Query(ctx context.Context, sql string, opts ...query.ExecuteOption) (query.Result, error) diff --git a/internal/config/v_two.json b/internal/config/v_two.json index acf914997d..fd7084d6e8 100644 --- a/internal/config/v_two.json +++ b/internal/config/v_two.json @@ -38,7 +38,8 @@ "enum": [ "postgresql", "mysql", - "sqlite" + "sqlite", + "ydb" ] }, "schema": { diff --git a/internal/sqltest/local/ydb.go b/internal/sqltest/local/ydb.go index 3505d84059..5b1b5451a2 100644 --- a/internal/sqltest/local/ydb.go +++ b/internal/sqltest/local/ydb.go @@ -5,7 +5,6 @@ import ( "fmt" "hash/fnv" "math/rand" - "net" "os" "testing" "time" @@ -39,10 +38,6 @@ func link_YDB(t *testing.T, migrations []string, rw bool) *ydb.Driver { if dbuiri == "" { t.Skip("YDB_SERVER_URI is empty") } - host, _, err := net.SplitHostPort(dbuiri) - if err != nil { - t.Fatalf("invalid YDB_SERVER_URI: %q", dbuiri) - } baseDB := os.Getenv("YDB_DATABASE") if baseDB == "" { @@ -77,9 +72,6 @@ func link_YDB(t *testing.T, migrations []string, rw bool) *ydb.Driver { db, err := ydb.Open(ctx, connectionString, ydb.WithInsecure(), ydb.WithDiscoveryInterval(time.Hour), - ydb.WithNodeAddressMutator(func(_ string) string { - return host - }), ) if err != nil { t.Fatalf("failed to open YDB connection: %s", err) From 286af626a002ea33fbd798a50052793ec3ff5336 Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov <150552906+1NepuNep1@users.noreply.github.com> Date: Fri, 29 Aug 2025 20:19:38 +0300 Subject: [PATCH 08/12] Update examples/authors/ydb/db_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- examples/authors/ydb/db_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/authors/ydb/db_test.go b/examples/authors/ydb/db_test.go index eeb9a6e97e..449e2dc0d8 100644 --- a/examples/authors/ydb/db_test.go +++ b/examples/authors/ydb/db_test.go @@ -75,7 +75,7 @@ func TestAuthors(t *testing.T) { P2: 10, } - authors, err := q.UpdateAuthorByID(ctx, arg, query.WithIdempotent(), query.WithIdempotent()) + authors, err := q.UpdateAuthorByID(ctx, arg, query.WithIdempotent()) if err != nil { t.Fatal(err) } From 7a6eea6357098f0a9b787245b0f89803429cd1d6 Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov <150552906+1NepuNep1@users.noreply.github.com> Date: Fri, 29 Aug 2025 20:19:53 +0300 Subject: [PATCH 09/12] Update internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl b/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl index be8eff1094..4f7f6fd817 100644 --- a/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl +++ b/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl @@ -173,7 +173,6 @@ func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBA {{else}} var items []{{.Ret.DefineType}} {{end -}} - for set, err := range result.ResultSets(ctx) { if err != nil { {{- if $.WrapErrors}} From 4acf1a00f5f9ee1a2929fc3f5b9a35c17122a96e Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov Date: Fri, 29 Aug 2025 20:55:36 +0300 Subject: [PATCH 10/12] Added grpcs ydb connection version to local.YDB --- internal/sqltest/local/ydb.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/internal/sqltest/local/ydb.go b/internal/sqltest/local/ydb.go index 5b1b5451a2..14287c5460 100644 --- a/internal/sqltest/local/ydb.go +++ b/internal/sqltest/local/ydb.go @@ -21,14 +21,22 @@ func init() { } func YDB(t *testing.T, migrations []string) *ydb.Driver { - return link_YDB(t, migrations, true) + return link_YDB(t, migrations, true, false) +} + +func YDBTLS(t *testing.T, migrations []string) *ydb.Driver { + return link_YDB(t, migrations, true, true) } func ReadOnlyYDB(t *testing.T, migrations []string) *ydb.Driver { - return link_YDB(t, migrations, false) + return link_YDB(t, migrations, false, false) +} + +func ReadOnlyYDBTLS(t *testing.T, migrations []string) *ydb.Driver { + return link_YDB(t, migrations, false, true) } -func link_YDB(t *testing.T, migrations []string, rw bool) *ydb.Driver { +func link_YDB(t *testing.T, migrations []string, rw bool, tls bool) *ydb.Driver { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -65,8 +73,12 @@ func link_YDB(t *testing.T, migrations []string, rw bool) *ydb.Driver { } else { name = fmt.Sprintf("sqlc_test_%x", h.Sum(nil)) } - - connectionString := fmt.Sprintf("grpc://%s%s", dbuiri, baseDB) + var connectionString string + if tls { + connectionString = fmt.Sprintf("grpcs://%s%s", dbuiri, baseDB) + } else { + connectionString = fmt.Sprintf("grpc://%s%s", dbuiri, baseDB) + } t.Logf("→ Opening YDB connection: %s", connectionString) db, err := ydb.Open(ctx, connectionString, From 02fb746c62b6bfe4363e61d24cf39ebe31c059c0 Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov Date: Mon, 1 Sep 2025 16:20:19 +0300 Subject: [PATCH 11/12] Rewrited some examples & internal/sqltest/local/ydb logic to be a bit more trivial --- examples/authors/ydb/db_test.go | 7 +++++ examples/authors/ydb/query.sql | 5 +++- examples/authors/ydb/query.sql.go | 12 ++++++++ internal/sqltest/local/ydb.go | 47 ++++++++----------------------- 4 files changed, 34 insertions(+), 37 deletions(-) diff --git a/examples/authors/ydb/db_test.go b/examples/authors/ydb/db_test.go index 449e2dc0d8..9f8bf5c837 100644 --- a/examples/authors/ydb/db_test.go +++ b/examples/authors/ydb/db_test.go @@ -169,4 +169,11 @@ func TestAuthors(t *testing.T) { t.Fatalf("expected no authors, got %d", len(authors)) } }) + + t.Run("Drop Table Authors", func(t *testing.T) { + err := q.DropTable(ctx) + if err != nil { + t.Fatal(err) + } + }) } diff --git a/examples/authors/ydb/query.sql b/examples/authors/ydb/query.sql index e54b05ae0d..e4bbafd187 100644 --- a/examples/authors/ydb/query.sql +++ b/examples/authors/ydb/query.sql @@ -29,4 +29,7 @@ UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) RETURNING bio; DELETE FROM authors WHERE id = $p0; -- name: UpdateAuthorByID :queryrows -UPDATE authors SET name = $p0, bio = $p1 WHERE id = $p2 RETURNING *; \ No newline at end of file +UPDATE authors SET name = $p0, bio = $p1 WHERE id = $p2 RETURNING *; + +-- name: DropTable :exec +DROP TABLE IF EXISTS authors; \ No newline at end of file diff --git a/examples/authors/ydb/query.sql.go b/examples/authors/ydb/query.sql.go index 7c9511e7ec..2b8b5685e8 100644 --- a/examples/authors/ydb/query.sql.go +++ b/examples/authors/ydb/query.sql.go @@ -128,6 +128,18 @@ func (q *Queries) DeleteAuthor(ctx context.Context, p0 uint64, opts ...query.Exe return nil } +const dropTable = `-- name: DropTable :exec +DROP TABLE IF EXISTS authors +` + +func (q *Queries) DropTable(ctx context.Context, opts ...query.ExecuteOption) error { + err := q.db.Exec(ctx, dropTable, opts...) + if err != nil { + return xerrors.WithStackTrace(err) + } + return nil +} + const getAuthor = `-- name: GetAuthor :one SELECT id, name, bio FROM authors WHERE id = $p0 diff --git a/internal/sqltest/local/ydb.go b/internal/sqltest/local/ydb.go index 14287c5460..e3e51e3716 100644 --- a/internal/sqltest/local/ydb.go +++ b/internal/sqltest/local/ydb.go @@ -3,7 +3,6 @@ package local import ( "context" "fmt" - "hash/fnv" "math/rand" "os" "testing" @@ -13,7 +12,6 @@ import ( "github.com/sqlc-dev/sqlc/internal/sql/sqlpath" "github.com/ydb-platform/ydb-go-sdk/v3" "github.com/ydb-platform/ydb-go-sdk/v3/query" - "github.com/ydb-platform/ydb-go-sdk/v3/sugar" ) func init() { @@ -37,11 +35,11 @@ func ReadOnlyYDBTLS(t *testing.T, migrations []string) *ydb.Driver { } func link_YDB(t *testing.T, migrations []string, rw bool, tls bool) *ydb.Driver { - t.Helper() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() + t.Helper() + dbuiri := os.Getenv("YDB_SERVER_URI") if dbuiri == "" { t.Skip("YDB_SERVER_URI is empty") @@ -52,34 +50,12 @@ func link_YDB(t *testing.T, migrations []string, rw bool, tls bool) *ydb.Driver baseDB = "/local" } - var seed []string - files, err := sqlpath.Glob(migrations) - if err != nil { - t.Fatal(err) - } - h := fnv.New64() - for _, f := range files { - blob, err := os.ReadFile(f) - if err != nil { - t.Fatal(err) - } - h.Write(blob) - seed = append(seed, migrate.RemoveRollbackStatements(string(blob))) - } - - var name string - if rw { - name = fmt.Sprintf("sqlc_test_%s", "test_new") - } else { - name = fmt.Sprintf("sqlc_test_%x", h.Sum(nil)) - } var connectionString string if tls { connectionString = fmt.Sprintf("grpcs://%s%s", dbuiri, baseDB) } else { connectionString = fmt.Sprintf("grpc://%s%s", dbuiri, baseDB) } - t.Logf("→ Opening YDB connection: %s", connectionString) db, err := ydb.Open(ctx, connectionString, ydb.WithInsecure(), @@ -89,20 +65,19 @@ func link_YDB(t *testing.T, migrations []string, rw bool, tls bool) *ydb.Driver t.Fatalf("failed to open YDB connection: %s", err) } - prefix := fmt.Sprintf("%s/%s", baseDB, name) - t.Logf("→ Using prefix: %s", prefix) - - err = sugar.RemoveRecursive(ctx, db, prefix) + files, err := sqlpath.Glob(migrations) if err != nil { - t.Logf("Warning: failed to remove old data: %s", err) + t.Fatal(err) } - t.Log("→ Applying migrations to prefix: ", prefix) + for _, f := range files { + blob, err := os.ReadFile(f) + if err != nil { + t.Fatal(err) + } + stmt := migrate.RemoveRollbackStatements(string(blob)) - for _, stmt := range seed { - err := db.Query().Exec(ctx, stmt, - query.WithTxControl(query.EmptyTxControl()), - ) + err = db.Query().Exec(ctx, stmt, query.WithTxControl(query.EmptyTxControl())) if err != nil { t.Fatalf("failed to apply migration: %s\nSQL: %s", err, stmt) } From ed77ee02b81deb7b64fb44125f76c134360a5225 Mon Sep 17 00:00:00 2001 From: Viktor Pentyukhov Date: Tue, 2 Sep 2025 14:21:50 +0300 Subject: [PATCH 12/12] Got rid off :queryrows (now many uses .Query(...) instead of .QueryResultSet(...). + Refactored some test examples --- examples/authors/ydb/db_test.go | 79 -------- examples/authors/ydb/query.sql | 26 +-- examples/authors/ydb/query.sql.go | 168 +----------------- internal/codegen/golang/gen.go | 20 +-- internal/codegen/golang/query.go | 3 +- internal/codegen/golang/result.go | 1 - .../templates/ydb-go-sdk/interfaceCode.tmpl | 9 - .../templates/ydb-go-sdk/queryCode.tmpl | 117 +++--------- internal/metadata/meta.go | 3 +- 9 files changed, 38 insertions(+), 388 deletions(-) diff --git a/examples/authors/ydb/db_test.go b/examples/authors/ydb/db_test.go index 9f8bf5c837..6ab15913f0 100644 --- a/examples/authors/ydb/db_test.go +++ b/examples/authors/ydb/db_test.go @@ -45,49 +45,6 @@ func TestAuthors(t *testing.T) { } }) - t.Run("CreateOrUpdateAuthorReturningBio", func(t *testing.T) { - newBio := "Обновленная биография автора" - arg := CreateOrUpdateAuthorReturningBioParams{ - P0: 3, - P1: "Тестовый Автор", - P2: &newBio, - } - - returnedBio, err := q.CreateOrUpdateAuthorReturningBio(ctx, arg, query.WithIdempotent()) - if err != nil { - t.Fatalf("failed to create or update author: %v", err) - } - - if returnedBio == nil { - t.Fatal("expected non-nil bio, got nil") - } - if *returnedBio != newBio { - t.Fatalf("expected bio %q, got %q", newBio, *returnedBio) - } - - t.Logf("Author created or updated successfully with bio: %s", *returnedBio) - }) - - t.Run("Update Author", func(t *testing.T) { - arg := UpdateAuthorByIDParams{ - P0: "Максим Горький", - P1: ptr("Обновленная биография"), - P2: 10, - } - - authors, err := q.UpdateAuthorByID(ctx, arg, query.WithIdempotent()) - if err != nil { - t.Fatal(err) - } - for _, a := range authors { - bio := "Null" - if a.Bio != nil { - bio = *a.Bio - } - t.Logf("- ID: %d | Name: %s | Bio: %s", a.ID, a.Name, bio) - } - }) - t.Run("ListAuthors", func(t *testing.T) { authors, err := q.ListAuthors(ctx) if err != nil { @@ -118,42 +75,6 @@ func TestAuthors(t *testing.T) { t.Logf("- ID: %d | Name: %s | Bio: %s", singleAuthor.ID, singleAuthor.Name, bio) }) - t.Run("GetAuthorByName", func(t *testing.T) { - authors, err := q.GetAuthorsByName(ctx, "Александр Пушкин") - if err != nil { - t.Fatal(err) - } - if len(authors) == 0 { - t.Fatal("expected at least one author with this name, got none") - } - t.Log("Authors with this name:") - for _, a := range authors { - bio := "Null" - if a.Bio != nil { - bio = *a.Bio - } - t.Logf("- ID: %d | Name: %s | Bio: %s", a.ID, a.Name, bio) - } - }) - - t.Run("ListAuthorsWithNullBio", func(t *testing.T) { - authors, err := q.ListAuthorsWithNullBio(ctx) - if err != nil { - t.Fatal(err) - } - if len(authors) == 0 { - t.Fatal("expected at least one author with NULL bio, got none") - } - t.Log("Authors with NULL bio:") - for _, a := range authors { - bio := "Null" - if a.Bio != nil { - bio = *a.Bio - } - t.Logf("- ID: %d | Name: %s | Bio: %s", a.ID, a.Name, bio) - } - }) - t.Run("Delete All Authors", func(t *testing.T) { var i uint64 for i = 1; i <= 13; i++ { diff --git a/examples/authors/ydb/query.sql b/examples/authors/ydb/query.sql index e4bbafd187..804150615d 100644 --- a/examples/authors/ydb/query.sql +++ b/examples/authors/ydb/query.sql @@ -1,35 +1,15 @@ --- name: ListAuthors :many -SELECT * FROM authors; - -- name: GetAuthor :one SELECT * FROM authors -WHERE id = $p0; - --- name: GetAuthorsByName :many -SELECT * FROM authors -WHERE name = $p0; - --- name: ListAuthorsWithNullBio :many -SELECT * FROM authors -WHERE bio IS NULL; - --- name: Count :one -SELECT COUNT(*) FROM authors; +WHERE id = $p0 LIMIT 1; --- name: Coalesce :many -SELECT id, name, COALESCE(bio, 'Null value!') FROM authors; +-- name: ListAuthors :many +SELECT * FROM authors ORDER BY name; -- name: CreateOrUpdateAuthor :exec UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2); --- name: CreateOrUpdateAuthorReturningBio :one -UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) RETURNING bio; - -- name: DeleteAuthor :exec DELETE FROM authors WHERE id = $p0; --- name: UpdateAuthorByID :queryrows -UPDATE authors SET name = $p0, bio = $p1 WHERE id = $p2 RETURNING *; - -- name: DropTable :exec DROP TABLE IF EXISTS authors; \ No newline at end of file diff --git a/examples/authors/ydb/query.sql.go b/examples/authors/ydb/query.sql.go index 2b8b5685e8..7459482b3a 100644 --- a/examples/authors/ydb/query.sql.go +++ b/examples/authors/ydb/query.sql.go @@ -13,52 +13,6 @@ import ( "github.com/ydb-platform/ydb-go-sdk/v3/query" ) -const coalesce = `-- name: Coalesce :many -SELECT id, name, COALESCE(bio, 'Null value!') FROM authors -` - -type CoalesceRow struct { - ID uint64 `json:"id"` - Name string `json:"name"` - Bio string `json:"bio"` -} - -func (q *Queries) Coalesce(ctx context.Context, opts ...query.ExecuteOption) ([]CoalesceRow, error) { - res, err := q.db.QueryResultSet(ctx, coalesce, opts...) - if err != nil { - return nil, xerrors.WithStackTrace(err) - } - var items []CoalesceRow - for row := range res.Rows(ctx) { - var i CoalesceRow - if err := row.Scan(&i.ID, &i.Name, &i.Bio); err != nil { - return nil, xerrors.WithStackTrace(err) - } - items = append(items, i) - } - if err := res.Close(ctx); err != nil { - return nil, xerrors.WithStackTrace(err) - } - return items, nil -} - -const count = `-- name: Count :one -SELECT COUNT(*) FROM authors -` - -func (q *Queries) Count(ctx context.Context, opts ...query.ExecuteOption) (uint64, error) { - row, err := q.db.QueryRow(ctx, count, opts...) - var count uint64 - if err != nil { - return count, xerrors.WithStackTrace(err) - } - err = row.Scan(&count) - if err != nil { - return count, xerrors.WithStackTrace(err) - } - return count, nil -} - const createOrUpdateAuthor = `-- name: CreateOrUpdateAuthor :exec UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) ` @@ -83,35 +37,6 @@ func (q *Queries) CreateOrUpdateAuthor(ctx context.Context, arg CreateOrUpdateAu return nil } -const createOrUpdateAuthorReturningBio = `-- name: CreateOrUpdateAuthorReturningBio :one -UPSERT INTO authors (id, name, bio) VALUES ($p0, $p1, $p2) RETURNING bio -` - -type CreateOrUpdateAuthorReturningBioParams struct { - P0 uint64 `json:"p0"` - P1 string `json:"p1"` - P2 *string `json:"p2"` -} - -func (q *Queries) CreateOrUpdateAuthorReturningBio(ctx context.Context, arg CreateOrUpdateAuthorReturningBioParams, opts ...query.ExecuteOption) (*string, error) { - row, err := q.db.QueryRow(ctx, createOrUpdateAuthorReturningBio, - append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ - "$p0": arg.P0, - "$p1": arg.P1, - "$p2": arg.P2, - })))..., - ) - var bio *string - if err != nil { - return bio, xerrors.WithStackTrace(err) - } - err = row.Scan(&bio) - if err != nil { - return bio, xerrors.WithStackTrace(err) - } - return bio, nil -} - const deleteAuthor = `-- name: DeleteAuthor :exec DELETE FROM authors WHERE id = $p0 ` @@ -142,7 +67,7 @@ func (q *Queries) DropTable(ctx context.Context, opts ...query.ExecuteOption) er const getAuthor = `-- name: GetAuthor :one SELECT id, name, bio FROM authors -WHERE id = $p0 +WHERE id = $p0 LIMIT 1 ` func (q *Queries) GetAuthor(ctx context.Context, p0 uint64, opts ...query.ExecuteOption) (Author, error) { @@ -162,99 +87,12 @@ func (q *Queries) GetAuthor(ctx context.Context, p0 uint64, opts ...query.Execut return i, nil } -const getAuthorsByName = `-- name: GetAuthorsByName :many -SELECT id, name, bio FROM authors -WHERE name = $p0 -` - -func (q *Queries) GetAuthorsByName(ctx context.Context, p0 string, opts ...query.ExecuteOption) ([]Author, error) { - res, err := q.db.QueryResultSet(ctx, getAuthorsByName, - append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ - "$p0": p0, - })))..., - ) - if err != nil { - return nil, xerrors.WithStackTrace(err) - } - var items []Author - for row := range res.Rows(ctx) { - var i Author - if err := row.Scan(&i.ID, &i.Name, &i.Bio); err != nil { - return nil, xerrors.WithStackTrace(err) - } - items = append(items, i) - } - if err := res.Close(ctx); err != nil { - return nil, xerrors.WithStackTrace(err) - } - return items, nil -} - const listAuthors = `-- name: ListAuthors :many -SELECT id, name, bio FROM authors +SELECT id, name, bio FROM authors ORDER BY name ` func (q *Queries) ListAuthors(ctx context.Context, opts ...query.ExecuteOption) ([]Author, error) { - res, err := q.db.QueryResultSet(ctx, listAuthors, opts...) - if err != nil { - return nil, xerrors.WithStackTrace(err) - } - var items []Author - for row := range res.Rows(ctx) { - var i Author - if err := row.Scan(&i.ID, &i.Name, &i.Bio); err != nil { - return nil, xerrors.WithStackTrace(err) - } - items = append(items, i) - } - if err := res.Close(ctx); err != nil { - return nil, xerrors.WithStackTrace(err) - } - return items, nil -} - -const listAuthorsWithNullBio = `-- name: ListAuthorsWithNullBio :many -SELECT id, name, bio FROM authors -WHERE bio IS NULL -` - -func (q *Queries) ListAuthorsWithNullBio(ctx context.Context, opts ...query.ExecuteOption) ([]Author, error) { - res, err := q.db.QueryResultSet(ctx, listAuthorsWithNullBio, opts...) - if err != nil { - return nil, xerrors.WithStackTrace(err) - } - var items []Author - for row := range res.Rows(ctx) { - var i Author - if err := row.Scan(&i.ID, &i.Name, &i.Bio); err != nil { - return nil, xerrors.WithStackTrace(err) - } - items = append(items, i) - } - if err := res.Close(ctx); err != nil { - return nil, xerrors.WithStackTrace(err) - } - return items, nil -} - -const updateAuthorByID = `-- name: UpdateAuthorByID :queryrows -UPDATE authors SET name = $p0, bio = $p1 WHERE id = $p2 RETURNING id, name, bio -` - -type UpdateAuthorByIDParams struct { - P0 string `json:"p0"` - P1 *string `json:"p1"` - P2 uint64 `json:"p2"` -} - -func (q *Queries) UpdateAuthorByID(ctx context.Context, arg UpdateAuthorByIDParams, opts ...query.ExecuteOption) ([]Author, error) { - result, err := q.db.Query(ctx, updateAuthorByID, - append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ - "$p0": arg.P0, - "$p1": arg.P1, - "$p2": arg.P2, - })))..., - ) + result, err := q.db.Query(ctx, listAuthors, opts...) if err != nil { return nil, xerrors.WithStackTrace(err) } diff --git a/internal/codegen/golang/gen.go b/internal/codegen/golang/gen.go index 15b6952ba0..35f76b90e7 100644 --- a/internal/codegen/golang/gen.go +++ b/internal/codegen/golang/gen.go @@ -39,7 +39,6 @@ type tmplCtx struct { EmitAllEnumValues bool UsesCopyFrom bool UsesBatch bool - UsesQueryRows bool OmitSqlcVersion bool BuildTags string WrapErrors bool @@ -184,7 +183,6 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum, EmitAllEnumValues: options.EmitAllEnumValues, UsesCopyFrom: usesCopyFrom(queries), UsesBatch: usesBatch(queries), - UsesQueryRows: usesQueryRows(queries), SQLDriver: parseDriver(options.SqlPackage), Q: "`", Package: options.Package, @@ -211,8 +209,13 @@ func generate(req *plugin.GenerateRequest, options *opts.Options, enums []Enum, return nil, errors.New(":batch* commands are only supported by pgx") } - if tctx.UsesQueryRows && tctx.SQLDriver != opts.SQLDriverYDBGoSDK { - return nil, errors.New(":queryrows commands are only supported by ydb-go-sdk") + if tctx.SQLDriver.IsYDBGoSDK() { + for _, q := range queries { + switch q.Cmd { + case metadata.CmdExecResult, metadata.CmdExecRows, metadata.CmdExecLastId: + return nil, fmt.Errorf("%s is not supported by ydb-go-sdk", q.Cmd) + } + } } funcMap := template.FuncMap{ @@ -359,15 +362,6 @@ func usesBatch(queries []Query) bool { return false } -func usesQueryRows(queries []Query) bool { - for _, q := range queries { - if q.Cmd == metadata.CmdQueryRows { - return true - } - } - return false -} - func checkNoTimesForMySQLCopyFrom(queries []Query) error { for _, q := range queries { if q.Cmd != metadata.CmdCopyFrom { diff --git a/internal/codegen/golang/query.go b/internal/codegen/golang/query.go index a299c95c87..02a09c3870 100644 --- a/internal/codegen/golang/query.go +++ b/internal/codegen/golang/query.go @@ -325,8 +325,7 @@ type Query struct { func (q Query) hasRetType() bool { scanned := q.Cmd == metadata.CmdOne || q.Cmd == metadata.CmdMany || - q.Cmd == metadata.CmdBatchMany || q.Cmd == metadata.CmdBatchOne || - q.Cmd == metadata.CmdQueryRows + q.Cmd == metadata.CmdBatchMany || q.Cmd == metadata.CmdBatchOne return scanned && !q.Ret.isEmpty() } diff --git a/internal/codegen/golang/result.go b/internal/codegen/golang/result.go index 8285f21b9e..515d0a654f 100644 --- a/internal/codegen/golang/result.go +++ b/internal/codegen/golang/result.go @@ -336,7 +336,6 @@ var cmdReturnsData = map[string]struct{}{ metadata.CmdBatchOne: {}, metadata.CmdMany: {}, metadata.CmdOne: {}, - metadata.CmdQueryRows: {}, } func putOutColumns(query *plugin.Query) bool { diff --git a/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl b/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl index 2aafd6b81a..f9c06cc705 100644 --- a/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl +++ b/internal/codegen/golang/templates/ydb-go-sdk/interfaceCode.tmpl @@ -29,15 +29,6 @@ {{end -}} {{.MethodName}}(ctx context.Context, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) error {{- end}} - {{- if and (eq .Cmd ":queryrows") ($dbtxParam) }} - {{range .Comments}}//{{.}} - {{end -}} - {{.MethodName}}(ctx context.Context, db DBTX, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) - {{- else if eq .Cmd ":queryrows"}} - {{range .Comments}}//{{.}} - {{end -}} - {{.MethodName}}(ctx context.Context, {{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) - {{- end}} {{- end}} } diff --git a/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl b/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl index 4f7f6fd817..ecd78b1344 100644 --- a/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl +++ b/internal/codegen/golang/templates/ydb-go-sdk/queryCode.tmpl @@ -58,100 +58,6 @@ func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBA {{if eq .Cmd ":many"}} {{range .Comments}}//{{.}} {{end -}} -func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBArgument}}db DBTX, {{end}}{{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) { - {{- $dbArg := "q.db" }}{{- if $.EmitMethodsWithDBArgument }}{{- $dbArg = "db" }}{{- end -}} - {{- if .Arg.IsEmpty }} - res, err := {{$dbArg}}.QueryResultSet(ctx, {{.ConstantName}}, opts...) - {{- else }} - res, err := {{$dbArg}}.QueryResultSet(ctx, {{.ConstantName}}, - append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ {{.Arg.YDBParamMapEntries}} })))..., - ) - {{- end }} - if err != nil { - {{- if $.WrapErrors}} - return nil, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) - {{- else }} - return nil, xerrors.WithStackTrace(err) - {{- end }} - } - {{- if $.EmitEmptySlices}} - items := []{{.Ret.DefineType}}{} - {{else}} - var items []{{.Ret.DefineType}} - {{end -}} - for row := range res.Rows(ctx) { - var {{.Ret.Name}} {{.Ret.Type}} - if err := row.Scan({{.Ret.Scan}}); err != nil { - {{- if $.WrapErrors}} - return nil, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) - {{- else }} - return nil, xerrors.WithStackTrace(err) - {{- end }} - } - items = append(items, {{.Ret.ReturnName}}) - } - if err := res.Close(ctx); err != nil { - {{- if $.WrapErrors}} - return nil, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) - {{- else }} - return nil, xerrors.WithStackTrace(err) - {{- end }} - } - return items, nil -} -{{end}} - -{{if eq .Cmd ":exec"}} -{{range .Comments}}//{{.}} -{{end -}} -func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBArgument}}db DBTX, {{end}}{{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) error { - {{- $dbArg := "q.db" }}{{- if $.EmitMethodsWithDBArgument }}{{- $dbArg = "db" }}{{- end -}} - {{- if .Arg.IsEmpty }} - err := {{$dbArg}}.Exec(ctx, {{.ConstantName}}, opts...) - {{- else }} - err := {{$dbArg}}.Exec(ctx, {{.ConstantName}}, - append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ {{.Arg.YDBParamMapEntries}} })))..., - ) - {{- end }} - if err != nil { - {{- if $.WrapErrors }} - return xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) - {{- else }} - return xerrors.WithStackTrace(err) - {{- end }} - } - return nil -} -{{end}} - -{{if eq .Cmd ":execresult"}} -{{range .Comments}}//{{.}} -{{end -}} -func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBArgument}}db DBTX, {{end}}{{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) (query.Result, error) { - {{- $dbArg := "q.db" }}{{- if $.EmitMethodsWithDBArgument }}{{- $dbArg = "db" }}{{- end -}} - {{- if .Arg.IsEmpty }} - result, err := {{$dbArg}}.Query(ctx, {{.ConstantName}}, opts...) - {{- else }} - result, err := {{$dbArg}}.Query(ctx, {{.ConstantName}}, - append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ {{.Arg.YDBParamMapEntries}} })))..., - ) - {{- end }} - {{- if $.WrapErrors}} - if err != nil { - return result, xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) - } - {{- else }} - if err != nil { - return result, xerrors.WithStackTrace(err) - } - {{- end}} - return result, nil -} -{{end}} - -{{if eq .Cmd ":queryrows"}} -{{range .Comments}}//{{.}} -{{end -}} func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBArgument}}db DBTX, {{end}}{{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) ([]{{.Ret.DefineType}}, error) { {{- $dbArg := "q.db" }}{{- if $.EmitMethodsWithDBArgument }}{{- $dbArg = "db" }}{{- end -}} {{- if .Arg.IsEmpty }} @@ -211,6 +117,29 @@ func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBA } {{end}} +{{if eq .Cmd ":exec"}} +{{range .Comments}}//{{.}} +{{end -}} +func (q *Queries) {{.MethodName}}(ctx context.Context, {{if $.EmitMethodsWithDBArgument}}db DBTX, {{end}}{{if not .Arg.IsEmpty}}{{.Arg.Pair}}, {{end}}opts ...query.ExecuteOption) error { + {{- $dbArg := "q.db" }}{{- if $.EmitMethodsWithDBArgument }}{{- $dbArg = "db" }}{{- end -}} + {{- if .Arg.IsEmpty }} + err := {{$dbArg}}.Exec(ctx, {{.ConstantName}}, opts...) + {{- else }} + err := {{$dbArg}}.Exec(ctx, {{.ConstantName}}, + append(opts, query.WithParameters(ydb.ParamsFromMap(map[string]any{ {{.Arg.YDBParamMapEntries}} })))..., + ) + {{- end }} + if err != nil { + {{- if $.WrapErrors }} + return xerrors.WithStackTrace(fmt.Errorf("query {{.MethodName}}: %w", err)) + {{- else }} + return xerrors.WithStackTrace(err) + {{- end }} + } + return nil +} +{{end}} + {{end}} {{end}} {{end}} diff --git a/internal/metadata/meta.go b/internal/metadata/meta.go index c9a6657a8e..8f63624d2c 100644 --- a/internal/metadata/meta.go +++ b/internal/metadata/meta.go @@ -37,7 +37,6 @@ const ( CmdBatchExec = ":batchexec" CmdBatchMany = ":batchmany" CmdBatchOne = ":batchone" - CmdQueryRows = ":queryrows" ) // A query name must be a valid Go identifier @@ -107,7 +106,7 @@ func ParseQueryNameAndType(t string, commentStyle CommentSyntax) (string, string queryName := part[2] queryType := strings.TrimSpace(part[3]) switch queryType { - case CmdOne, CmdMany, CmdExec, CmdExecResult, CmdExecRows, CmdExecLastId, CmdCopyFrom, CmdBatchExec, CmdBatchMany, CmdBatchOne, CmdQueryRows: + case CmdOne, CmdMany, CmdExec, CmdExecResult, CmdExecRows, CmdExecLastId, CmdCopyFrom, CmdBatchExec, CmdBatchMany, CmdBatchOne: default: return "", "", fmt.Errorf("invalid query type: %s", queryType) }