diff --git a/Makefile b/Makefile index b8745e57dc..18d6ca91b5 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build build-endtoend test test-ci test-examples test-endtoend start psql mysqlsh proto +.PHONY: build build-endtoend test test-ci test-examples test-endtoend start psql mysqlsh proto sqlc-dev ydb test-examples-ydb gen-examples-ydb build: go build ./... @@ -18,13 +18,21 @@ vet: test-examples: go test --tags=examples ./... +ydb-examples: sqlc-dev ydb gen-examples-ydb test-examples-ydb + +test-examples-ydb: + YDB_SERVER_URI=localhost:2136 go test -v ./examples/authors/ydb/... -count=1 + +gen-examples-ydb: + cd examples/authors/ && SQLCDEBUG=1 ~/bin/sqlc-dev generate && cd ../.. + build-endtoend: cd ./internal/endtoend/testdata && go build ./... test-ci: test-examples build-endtoend vet sqlc-dev: - go build -o ~/bin/sqlc-dev ./cmd/sqlc/ + go build -x -v -o ~/bin/sqlc-dev ./cmd/sqlc/ sqlc-pg-gen: go build -o ~/bin/sqlc-pg-gen ./internal/tools/sqlc-pg-gen @@ -38,6 +46,9 @@ test-json-process-plugin: start: docker compose up -d +ydb: + docker compose up -d ydb + fmt: go fmt ./... diff --git a/docker-compose.yml b/docker-compose.yml index 1173e8a14a..c7d9c50db0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,3 +19,16 @@ services: POSTGRES_DB: postgres POSTGRES_PASSWORD: mysecretpassword POSTGRES_USER: postgres + + ydb: + image: ydbplatform/local-ydb:latest + ports: + - "2135:2135" + - "2136:2136" + - "8765:8765" + restart: always + environment: + - YDB_USE_IN_MEMORY_PDISKS=true + - GRPC_TLS_PORT=2135 + - GRPC_PORT=2136 + - MON_PORT=8765 diff --git a/examples/authors/sqlc.yaml b/examples/authors/sqlc.yaml index 57f2319ea1..143cb608b0 100644 --- a/examples/authors/sqlc.yaml +++ b/examples/authors/sqlc.yaml @@ -43,6 +43,17 @@ sql: go: package: authors out: sqlite +- name: ydb + schema: ydb/schema.sql + queries: ydb/query.sql + engine: ydb + gen: + go: + package: authors + out: ydb + emit_json_tags: true + + rules: - name: postgresql-query-too-costly message: "Too costly" diff --git a/examples/authors/ydb/README.md b/examples/authors/ydb/README.md new file mode 100644 index 0000000000..9e77fc7886 --- /dev/null +++ b/examples/authors/ydb/README.md @@ -0,0 +1,47 @@ +# Инструкция по генерации + +В файлах `schema.sql` и `query.sql` записаны, соответственно, схема базы данных и запросы, из которых вы хотите сгенерировать код к базе данных. +В `db_test.go` находятся тесты для последних сгенерированных команд. +Ниже находятся команды для генерации и запуска тестов. + +--- + +### 1. Создание бинарника sqlc + +```bash +make sqlc-dev +``` + +### 2. Запуск YDB через Docker Compose + +```bash +make ydb +``` + +### 3. Генерация кода для примеров для YDB + +```bash +make gen-examples-ydb +``` + +### 4. Запуск тестов примеров для YDB + +```bash +make test-examples-ydb +``` + +### 5. Полный цикл: сборка, генерация, тестирование (удобно одной командой) + +```bash +make ydb-examples +``` + +Эта команда выполнит: + +- Сборку `sqlc-dev` +- Запуск контейнера YDB +- Генерацию примеров +- Тестирование примеров + +--- + diff --git a/examples/authors/ydb/db.go b/examples/authors/ydb/db.go new file mode 100644 index 0000000000..e2b0a86b13 --- /dev/null +++ b/examples/authors/ydb/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package authors + +import ( + "context" + "database/sql" +) + +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 +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/examples/authors/ydb/db_test.go b/examples/authors/ydb/db_test.go new file mode 100644 index 0000000000..76b37306ef --- /dev/null +++ b/examples/authors/ydb/db_test.go @@ -0,0 +1,169 @@ +package authors + +import ( + "context" + "testing" + + "github.com/sqlc-dev/sqlc/internal/sqltest/local" + _ "github.com/ydb-platform/ydb-go-sdk/v3" +) + +func ptr(s string) *string { + return &s +} + +func TestAuthors(t *testing.T) { + ctx := context.Background() + + test := local.YDB(t, []string{"schema.sql"}) + defer test.DB.Close() + + q := New(test.DB) + + t.Run("InsertAuthors", func(t *testing.T) { + authorsToInsert := []CreateOrUpdateAuthorParams{ + {P0: 1, P1: "Лев Толстой", P2: ptr("Русский писатель, автор \"Война и мир\"")}, + {P0: 2, P1: "Александр Пушкин", P2: ptr("Автор \"Евгения Онегина\"")}, + {P0: 3, P1: "Александр Пушкин", P2: ptr("Русский поэт, драматург и прозаик")}, + {P0: 4, P1: "Фёдор Достоевский", P2: ptr("Автор \"Преступление и наказание\"")}, + {P0: 5, P1: "Николай Гоголь", P2: ptr("Автор \"Мёртвые души\"")}, + {P0: 6, P1: "Антон Чехов", P2: nil}, + {P0: 7, P1: "Иван Тургенев", P2: ptr("Автор \"Отцы и дети\"")}, + {P0: 8, P1: "Михаил Лермонтов", P2: nil}, + {P0: 9, P1: "Даниил Хармс", P2: ptr("Абсурдист, писатель и поэт")}, + {P0: 10, P1: "Максим Горький", P2: ptr("Автор \"На дне\"")}, + {P0: 11, P1: "Владимир Маяковский", P2: nil}, + {P0: 12, P1: "Сергей Есенин", P2: ptr("Русский лирик")}, + {P0: 13, P1: "Борис Пастернак", P2: ptr("Автор \"Доктор Живаго\"")}, + } + + for _, author := range authorsToInsert { + if _, err := q.CreateOrUpdateAuthor(ctx, author); err != nil { + t.Fatalf("failed to insert author %q: %v", author.P1, err) + } + } + }) + + t.Run("CreateOrUpdateAuthorReturningBio", func(t *testing.T) { + newBio := "Обновленная биография автора" + arg := CreateOrUpdateAuthorReturningBioParams{ + P0: 3, + P1: "Тестовый Автор", + P2: &newBio, + } + + returnedBio, err := q.CreateOrUpdateAuthorReturningBio(ctx, arg) + 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, + } + + singleAuthor, err := q.UpdateAuthorByID(ctx, arg) + if err != nil { + t.Fatal(err) + } + bio := "Null" + if singleAuthor.Bio != nil { + bio = *singleAuthor.Bio + } + t.Logf("- ID: %d | Name: %s | Bio: %s", singleAuthor.ID, singleAuthor.Name, bio) + }) + + t.Run("ListAuthors", func(t *testing.T) { + authors, err := q.ListAuthors(ctx) + if err != nil { + t.Fatal(err) + } + if len(authors) == 0 { + t.Fatal("expected at least one author, got none") + } + t.Log("Authors:") + 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("GetAuthor", func(t *testing.T) { + singleAuthor, err := q.GetAuthor(ctx, 10) + if err != nil { + t.Fatal(err) + } + bio := "Null" + if singleAuthor.Bio != nil { + bio = *singleAuthor.Bio + } + 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++ { + if err := q.DeleteAuthor(ctx, i); err != nil { + t.Fatalf("failed to delete authors: %v", err) + } + } + authors, err := q.ListAuthors(ctx) + if err != nil { + t.Fatal(err) + } + if len(authors) != 0 { + t.Fatalf("expected no authors, got %d", len(authors)) + } + }) +} diff --git a/examples/authors/ydb/models.go b/examples/authors/ydb/models.go new file mode 100644 index 0000000000..12b4f3a604 --- /dev/null +++ b/examples/authors/ydb/models.go @@ -0,0 +1,11 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 + +package authors + +type Author struct { + ID uint64 `json:"id"` + Name string `json:"name"` + Bio *string `json:"bio"` +} diff --git a/examples/authors/ydb/query.sql b/examples/authors/ydb/query.sql new file mode 100644 index 0000000000..bf672042c5 --- /dev/null +++ b/examples/authors/ydb/query.sql @@ -0,0 +1,32 @@ +-- 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; + +-- 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: DeleteAuthor :exec +DELETE FROM authors WHERE id = $p0; + +-- name: UpdateAuthorByID :one +UPDATE authors SET name = $p0, bio = $p1 WHERE id = $p2 RETURNING *; diff --git a/examples/authors/ydb/query.sql.go b/examples/authors/ydb/query.sql.go new file mode 100644 index 0000000000..45d86c96fd --- /dev/null +++ b/examples/authors/ydb/query.sql.go @@ -0,0 +1,207 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.29.0 +// source: query.sql + +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 +} + +const count = `-- name: Count :one +SELECT COUNT(*) FROM authors +` + +func (q *Queries) Count(ctx context.Context) (uint64, error) { + row := q.db.QueryRowContext(ctx, count) + 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 +} + +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 +} + +const getAuthor = `-- name: GetAuthor :one +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) + var i Author + err := row.Scan(&i.ID, &i.Name, &i.Bio) + return i, err +} + +const getAuthorsByName = `-- name: GetAuthorsByName :many +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) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + 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 +} + +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) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + 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 +} + +const listAuthorsWithNullBio = `-- name: ListAuthorsWithNullBio :many +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) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Author + for rows.Next() { + var i Author + 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 +} + +const updateAuthorByID = `-- name: UpdateAuthorByID :one +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) (Author, error) { + row := q.db.QueryRowContext(ctx, updateAuthorByID, arg.P0, arg.P1, arg.P2) + var i Author + err := row.Scan(&i.ID, &i.Name, &i.Bio) + return i, err +} diff --git a/examples/authors/ydb/schema.sql b/examples/authors/ydb/schema.sql new file mode 100644 index 0000000000..5207fb3b1e --- /dev/null +++ b/examples/authors/ydb/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE authors ( + id Uint64, + name Utf8 NOT NULL, + bio Utf8, + PRIMARY KEY (id) +); diff --git a/go.mod b/go.mod index 3f400daed9..b5a03ba6c2 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,8 @@ 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/yql-parsers v0.0.0-20250309001738-7d693911f333 golang.org/x/sync v0.13.0 google.golang.org/grpc v1.72.0 google.golang.org/protobuf v1.36.6 @@ -35,6 +37,7 @@ require ( cel.dev/expr v0.23.1 // indirect filippo.io/edwards25519 v1.1.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect @@ -45,6 +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/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 @@ -56,6 +60,7 @@ require ( github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect diff --git a/go.sum b/go.sum index 64414ebb7d..c5615aa00f 100644 --- a/go.sum +++ b/go.sum @@ -1,12 +1,24 @@ cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= @@ -20,8 +32,15 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= @@ -33,19 +52,43 @@ github.com/go-sql-driver/mysql v1.9.2/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI6 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY= github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= @@ -101,6 +144,8 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 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/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= @@ -143,10 +188,14 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rekby/fixenv v0.6.1 h1:jUFiSPpajT4WY2cYuc++7Y1zWrnCxnovGCIX72PZniM= +github.com/rekby/fixenv v0.6.1/go.mod h1:/b5LRc06BYJtslRtHKxsPWFT/ySpHV+rWvzTg+XWk4c= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/riza-io/grpc-go v0.2.0 h1:2HxQKFVE7VuYstcJ8zqpN84VnAoJ4dCL6YFhJewNcHQ= github.com/riza-io/grpc-go v0.2.0/go.mod h1:2bDvR9KkKC3KhtlSHfR3dAXjUMT86kg4UfWFyVGWqi8= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= @@ -174,8 +223,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfPe7z7go8Dvv1AJQDI3eQ/5xith3q2mFlo= @@ -188,6 +237,12 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20241112172322-ea1f63298f77 h1:LY6cI8cP4B9rrpTleZk95+08kl2gF4rixG7+V/dwL6Q= +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/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= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= @@ -201,6 +256,7 @@ go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -212,6 +268,8 @@ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= @@ -237,23 +295,39 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -264,7 +338,10 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= @@ -279,8 +356,11 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -295,13 +375,38 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= @@ -315,11 +420,14 @@ gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24 gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= diff --git a/internal/codegen/golang/go_type.go b/internal/codegen/golang/go_type.go index 0d72621efe..0a16c02a62 100644 --- a/internal/codegen/golang/go_type.go +++ b/internal/codegen/golang/go_type.go @@ -86,6 +86,8 @@ func goInnerType(req *plugin.GenerateRequest, options *opts.Options, col *plugin return postgresType(req, options, col) case "sqlite": return sqliteType(req, options, col) + case "ydb": + return YDBType(req, options, col) default: return "interface{}" } diff --git a/internal/codegen/golang/ydb_type.go b/internal/codegen/golang/ydb_type.go new file mode 100644 index 0000000000..e9e5c46344 --- /dev/null +++ b/internal/codegen/golang/ydb_type.go @@ -0,0 +1,169 @@ +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 YDBType(req *plugin.GenerateRequest, options *opts.Options, col *plugin.Column) string { + columnType := strings.ToLower(sdk.DataType(col.Type)) + notNull := col.NotNull || col.IsArray + emitPointersForNull := options.EmitPointersForNullTypes + + // https://ydb.tech/docs/ru/yql/reference/types/ + // ydb-go-sdk doesn't support sql.Null* yet + switch columnType { + // decimal types + case "bool": + if notNull { + return "bool" + } + if emitPointersForNull { + return "*bool" + } + // return "sql.NullBool" + return "*bool" + + case "int8": + if notNull { + return "int8" + } + if emitPointersForNull { + 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" + return "*int8" + case "int16": + if notNull { + return "int16" + } + if emitPointersForNull { + return "*int16" + } + // return "sql.NullInt16" + return "*int16" + case "int", "int32": //ydb doesn't have int type, but we need it to support untyped constants + if notNull { + return "int32" + } + if emitPointersForNull { + return "*int32" + } + // return "sql.NullInt32" + return "*int32" + case "int64": + if notNull { + return "int64" + } + if emitPointersForNull { + return "*int64" + } + // return "sql.NullInt64" + return "*int64" + + case "uint8": + if emitPointersForNull { + return "*uint8" + } + return "uint8" + case "uint16": + if emitPointersForNull { + return "*uint16" + } + return "uint16" + case "uint32": + if emitPointersForNull { + return "*uint32" + } + return "uint32" + case "uint64": + if emitPointersForNull { + return "*uint64" + } + return "uint64" + + case "float": + if notNull { + return "float32" + } + if emitPointersForNull { + return "*float32" + } + // The database/sql package does not have a sql.NullFloat32 type, so we + // use the smallest type they have which is NullFloat64 + // return "sql.NullFloat64" + return "*float32" + case "double": + if notNull { + return "float64" + } + if emitPointersForNull { + return "*float64" + } + // return "sql.NullFloat64" + return "*float64" + + // string types + case "string", "utf8", "text": + if notNull { + return "string" + } + if emitPointersForNull { + return "*string" + } + return "*string" + + // serial types + case "smallserial", "serial2": + if notNull { + return "int16" + } + if emitPointersForNull { + return "*int16" + } + // return "sql.NullInt16" + return "*int16" + + case "serial", "serial4": + if notNull { + return "int32" + } + if emitPointersForNull { + return "*int32" + } + // return "sql.NullInt32" + return "*int32" + + case "bigserial", "serial8": + if notNull { + return "int64" + } + if emitPointersForNull { + return "*int64" + } + // return "sql.NullInt64" + return "*int64" + + case "null": + // return "sql.Null" + return "interface{}" + + case "any": + return "interface{}" + + default: + if debug.Active { + log.Printf("unknown YDB type: %s\n", columnType) + } + + return "interface{}" + } + +} diff --git a/internal/compiler/engine.go b/internal/compiler/engine.go index d263637d9f..a2cc746b12 100644 --- a/internal/compiler/engine.go +++ b/internal/compiler/engine.go @@ -11,6 +11,7 @@ import ( "github.com/sqlc-dev/sqlc/internal/engine/postgresql" pganalyze "github.com/sqlc-dev/sqlc/internal/engine/postgresql/analyzer" "github.com/sqlc-dev/sqlc/internal/engine/sqlite" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" "github.com/sqlc-dev/sqlc/internal/opts" "github.com/sqlc-dev/sqlc/internal/sql/catalog" ) @@ -39,6 +40,9 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings) (*Compiler, err case config.EngineSQLite: c.parser = sqlite.NewParser() c.catalog = sqlite.NewCatalog() + case config.EngineYDB: + c.parser = ydb.NewParser() + c.catalog = ydb.NewCatalog() case config.EngineMySQL: c.parser = dolphin.NewParser() c.catalog = dolphin.NewCatalog() diff --git a/internal/config/config.go b/internal/config/config.go index 0ff805fccd..f7df94e5f8 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -54,6 +54,7 @@ const ( EngineMySQL Engine = "mysql" EnginePostgreSQL Engine = "postgresql" EngineSQLite Engine = "sqlite" + EngineYDB Engine = "ydb" ) type Config struct { diff --git a/internal/engine/ydb/catalog.go b/internal/engine/ydb/catalog.go new file mode 100644 index 0000000000..f191d936f3 --- /dev/null +++ b/internal/engine/ydb/catalog.go @@ -0,0 +1,19 @@ +package ydb + +import "github.com/sqlc-dev/sqlc/internal/sql/catalog" + + +func NewCatalog() *catalog.Catalog { + def := "main" + return &catalog.Catalog{ + DefaultSchema: def, + Schemas: []*catalog.Schema{ + defaultSchema(def), + }, + Extensions: map[string]struct{}{}, + } +} + +func NewTestCatalog() *catalog.Catalog { + return catalog.New("main") +} diff --git a/internal/engine/ydb/catalog_tests/alter_group_test.go b/internal/engine/ydb/catalog_tests/alter_group_test.go new file mode 100644 index 0000000000..eef9f919e9 --- /dev/null +++ b/internal/engine/ydb/catalog_tests/alter_group_test.go @@ -0,0 +1,122 @@ +package ydb_test + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func TestAlterGroup(t *testing.T) { + tests := []struct { + stmt string + expected ast.Node + }{ + { + stmt: `ALTER GROUP admins RENAME TO superusers`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.AlterRoleStmt{ + Role: &ast.RoleSpec{ + Rolename: strPtr("admins"), + Roletype: ast.RoleSpecType(1), + }, + Action: 1, + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("rename"), + Defaction: ast.DefElemAction(1), + Arg: &ast.String{Str: "superusers"}, + }, + }, + }, + }, + }, + }, + }, + { + stmt: `ALTER GROUP devs ADD USER alice, bob, carol`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.AlterRoleStmt{ + Role: &ast.RoleSpec{ + Rolename: strPtr("devs"), + Roletype: ast.RoleSpecType(1), + }, + Action: 1, + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("rolemembers"), + Defaction: ast.DefElemAction(3), + Arg: &ast.List{ + Items: []ast.Node{ + &ast.RoleSpec{Rolename: strPtr("alice"), Roletype: ast.RoleSpecType(1)}, + &ast.RoleSpec{Rolename: strPtr("bob"), Roletype: ast.RoleSpecType(1)}, + &ast.RoleSpec{Rolename: strPtr("carol"), Roletype: ast.RoleSpecType(1)}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + stmt: `ALTER GROUP ops DROP USER dan, erin`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.AlterRoleStmt{ + Role: &ast.RoleSpec{ + Rolename: strPtr("ops"), + Roletype: ast.RoleSpecType(1), + }, + Action: 1, + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("rolemembers"), + Defaction: ast.DefElemAction(4), + Arg: &ast.List{ + Items: []ast.Node{ + &ast.RoleSpec{Rolename: strPtr("dan"), Roletype: ast.RoleSpecType(1)}, + &ast.RoleSpec{Rolename: strPtr("erin"), Roletype: ast.RoleSpecType(1)}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + p := ydb.NewParser() + for _, tc := range tests { + t.Run(tc.stmt, func(t *testing.T) { + stmts, err := p.Parse(strings.NewReader(tc.stmt)) + if err != nil { + t.Fatalf("Ошибка парсинга запроса %q: %v", tc.stmt, err) + } + if len(stmts) == 0 { + t.Fatalf("Запрос %q не распарсен", tc.stmt) + } + + diff := cmp.Diff(tc.expected, &stmts[0], + cmpopts.IgnoreFields(ast.RawStmt{}, "StmtLocation", "StmtLen"), + cmpopts.IgnoreFields(ast.DefElem{}, "Location"), + cmpopts.IgnoreFields(ast.RoleSpec{}, "Location"), + cmpopts.IgnoreFields(ast.A_Const{}, "Location"), + ) + if diff != "" { + t.Errorf("Несовпадение AST (-ожидалось +получено):\n%s", diff) + } + }) + } +} diff --git a/internal/engine/ydb/catalog_tests/alter_user_test.go b/internal/engine/ydb/catalog_tests/alter_user_test.go new file mode 100644 index 0000000000..dd4dc5bb93 --- /dev/null +++ b/internal/engine/ydb/catalog_tests/alter_user_test.go @@ -0,0 +1,153 @@ +package ydb_test + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func TestAlterUser(t *testing.T) { + tests := []struct { + stmt string + expected ast.Node + }{ + { + stmt: `ALTER USER alice RENAME TO queen`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.AlterRoleStmt{ + Role: &ast.RoleSpec{ + Rolename: strPtr("alice"), + Roletype: ast.RoleSpecType(1), + }, + Action: 1, + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("rename"), + Arg: &ast.String{Str: "queen"}, + Defaction: ast.DefElemAction(1), + }, + }, + }, + }, + }, + }, + }, + { + stmt: `ALTER USER bob LOGIN`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.AlterRoleStmt{ + Role: &ast.RoleSpec{ + Rolename: strPtr("bob"), + Roletype: ast.RoleSpecType(1), + }, + Action: 1, + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("login"), + Arg: &ast.Boolean{Boolval: true}, + }, + }, + }, + }, + }, + }, + }, + { + stmt: `ALTER USER charlie NOLOGIN`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.AlterRoleStmt{ + Role: &ast.RoleSpec{ + Rolename: strPtr("charlie"), + Roletype: ast.RoleSpecType(1), + }, + Action: 1, + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("nologin"), + Arg: &ast.Boolean{Boolval: false}, + }, + }, + }, + }, + }, + }, + }, + { + stmt: `ALTER USER dave PASSWORD 'qwerty'`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.AlterRoleStmt{ + Role: &ast.RoleSpec{ + Rolename: strPtr("dave"), + Roletype: ast.RoleSpecType(1), + }, + Action: 1, + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("password"), + Arg: &ast.String{Str: "qwerty"}, + }, + }, + }, + }, + }, + }, + }, + { + stmt: `ALTER USER elena HASH 'abc123'`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.AlterRoleStmt{ + Role: &ast.RoleSpec{ + Rolename: strPtr("elena"), + Roletype: ast.RoleSpecType(1), + }, + Action: 1, + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("hash"), + Arg: &ast.String{Str: "abc123"}, + }, + }, + }, + }, + }, + }, + }, + } + + p := ydb.NewParser() + for _, tc := range tests { + t.Run(tc.stmt, func(t *testing.T) { + stmts, err := p.Parse(strings.NewReader(tc.stmt)) + if err != nil { + t.Fatalf("Ошибка парсинга запроса %q: %v", tc.stmt, err) + } + if len(stmts) == 0 { + t.Fatalf("Запрос %q не распарсен", tc.stmt) + } + + diff := cmp.Diff(tc.expected, &stmts[0], + cmpopts.IgnoreFields(ast.RawStmt{}, "StmtLocation", "StmtLen"), + cmpopts.IgnoreFields(ast.DefElem{}, "Location"), + cmpopts.IgnoreFields(ast.RoleSpec{}, "Location"), + cmpopts.IgnoreFields(ast.A_Const{}, "Location"), + ) + if diff != "" { + t.Errorf("Несовпадение AST (-ожидалось +получено):\n%s", diff) + } + }) + } +} diff --git a/internal/engine/ydb/catalog_tests/create_group_test.go b/internal/engine/ydb/catalog_tests/create_group_test.go new file mode 100644 index 0000000000..724e912168 --- /dev/null +++ b/internal/engine/ydb/catalog_tests/create_group_test.go @@ -0,0 +1,110 @@ +package ydb_test + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func TestCreateGroup(t *testing.T) { + tests := []struct { + stmt string + expected ast.Node + }{ + { + stmt: `CREATE GROUP group1`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.CreateRoleStmt{ + StmtType: ast.RoleStmtType(3), // CREATE GROUP + Role: strPtr("group1"), + Options: &ast.List{}, + }, + }, + }, + }, + { + stmt: `CREATE GROUP group1 WITH USER alice`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.CreateRoleStmt{ + StmtType: ast.RoleStmtType(3), + Role: strPtr("group1"), + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("rolemembers"), + Arg: &ast.List{ + Items: []ast.Node{ + &ast.RoleSpec{ + Roletype: ast.RoleSpecType(1), + Rolename: strPtr("alice"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + { + stmt: `CREATE GROUP group1 WITH USER alice, bebik`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.CreateRoleStmt{ + StmtType: ast.RoleStmtType(3), + Role: strPtr("group1"), + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("rolemembers"), + Arg: &ast.List{ + Items: []ast.Node{ + &ast.RoleSpec{ + Roletype: ast.RoleSpecType(1), + Rolename: strPtr("alice"), + }, + &ast.RoleSpec{ + Roletype: ast.RoleSpecType(1), + Rolename: strPtr("bebik"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + p := ydb.NewParser() + for _, tc := range tests { + t.Run(tc.stmt, func(t *testing.T) { + stmts, err := p.Parse(strings.NewReader(tc.stmt)) + if err != nil { + t.Fatalf("Ошибка парсинга запроса %q: %v", tc.stmt, err) + } + if len(stmts) == 0 { + t.Fatalf("Запрос %q не распарсен", tc.stmt) + } + + diff := cmp.Diff(tc.expected, &stmts[0], + cmpopts.IgnoreFields(ast.RawStmt{}, "StmtLocation", "StmtLen"), + cmpopts.IgnoreFields(ast.DefElem{}, "Location"), + cmpopts.IgnoreFields(ast.RoleSpec{}, "Location"), + cmpopts.IgnoreFields(ast.A_Const{}, "Location"), + ) + if diff != "" { + t.Errorf("Несовпадение AST (-ожидалось +получено):\n%s", diff) + } + }) + } +} diff --git a/internal/engine/ydb/catalog_tests/create_table_test.go b/internal/engine/ydb/catalog_tests/create_table_test.go new file mode 100644 index 0000000000..e98288d75a --- /dev/null +++ b/internal/engine/ydb/catalog_tests/create_table_test.go @@ -0,0 +1,166 @@ +package ydb_test + +import ( + "strconv" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" + "github.com/sqlc-dev/sqlc/internal/sql/ast" + "github.com/sqlc-dev/sqlc/internal/sql/catalog" +) + +func TestCreateTable(t *testing.T) { + tests := []struct { + stmt string + s *catalog.Schema + }{ + { + stmt: `CREATE TABLE users ( + id Uint64, + age Int32, + score Float, + PRIMARY KEY (id) + )`, + s: &catalog.Schema{ + Name: "main", + Tables: []*catalog.Table{ + { + Rel: &ast.TableName{Name: "users"}, + Columns: []*catalog.Column{ + { + Name: "id", + Type: ast.TypeName{Name: "Uint64"}, + IsNotNull: true, + }, + { + Name: "age", + Type: ast.TypeName{Name: "Int32"}, + }, + { + Name: "score", + Type: ast.TypeName{Name: "Float"}, + }, + }, + }, + }, + }, + }, + { + stmt: `CREATE TABLE posts ( + id Uint64, + title Utf8 NOT NULL, + content String, + metadata Json, + PRIMARY KEY (id) + )`, + s: &catalog.Schema{ + Name: "main", + Tables: []*catalog.Table{ + { + Rel: &ast.TableName{Name: "posts"}, + Columns: []*catalog.Column{ + { + Name: "id", + Type: ast.TypeName{Name: "Uint64"}, + IsNotNull: true, + }, + { + Name: "title", + Type: ast.TypeName{Name: "Utf8"}, + IsNotNull: true, + }, + { + Name: "content", + Type: ast.TypeName{Name: "String"}, + }, + { + Name: "metadata", + Type: ast.TypeName{Name: "Json"}, + }, + }, + }, + }, + }, + }, + { + stmt: `CREATE TABLE orders ( + id Uuid, + amount Decimal(22,9), + created_at Uint64, + PRIMARY KEY (id) + )`, + s: &catalog.Schema{ + Name: "main", + Tables: []*catalog.Table{ + { + Rel: &ast.TableName{Name: "orders"}, + Columns: []*catalog.Column{ + { + Name: "id", + Type: ast.TypeName{Name: "Uuid"}, + IsNotNull: true, + }, + { + Name: "amount", + Type: ast.TypeName{ + Name: "Decimal", + Names: &ast.List{ + Items: []ast.Node{ + &ast.Integer{Ival: 22}, + &ast.Integer{Ival: 9}, + }, + }, + }, + }, + { + Name: "created_at", + Type: ast.TypeName{Name: "Uint64"}, + }, + }, + }, + }, + }, + }, + } + + p := ydb.NewParser() + for i, tc := range tests { + test := tc + t.Run(strconv.Itoa(i), func(t *testing.T) { + stmts, err := p.Parse(strings.NewReader(test.stmt)) + if err != nil { + t.Log(test.stmt) + t.Fatal(err) + } + + c := ydb.NewTestCatalog() + if err := c.Build(stmts); err != nil { + t.Log(test.stmt) + t.Fatal(err) + } + + e := ydb.NewTestCatalog() + if test.s != nil { + var replaced bool + for i := range e.Schemas { + if e.Schemas[i].Name == test.s.Name { + e.Schemas[i] = test.s + replaced = true + break + } + } + if !replaced { + e.Schemas = append(e.Schemas, test.s) + } + } + + if diff := cmp.Diff(e, c, cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(catalog.Column{})); diff != "" { + t.Log(test.stmt) + t.Errorf("catalog mismatch:\n%s", diff) + } + }) + } +} diff --git a/internal/engine/ydb/catalog_tests/create_user_test.go b/internal/engine/ydb/catalog_tests/create_user_test.go new file mode 100644 index 0000000000..be53e9dd79 --- /dev/null +++ b/internal/engine/ydb/catalog_tests/create_user_test.go @@ -0,0 +1,129 @@ +package ydb_test + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func TestCreateUser(t *testing.T) { + tests := []struct { + stmt string + expected ast.Node + }{ + { + stmt: `CREATE USER alice`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.CreateRoleStmt{ + StmtType: ast.RoleStmtType(2), // CREATE USER + Role: strPtr("alice"), + Options: &ast.List{}, + }, + }, + }, + }, + { + stmt: `CREATE USER bob PASSWORD 'secret'`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.CreateRoleStmt{ + StmtType: ast.RoleStmtType(2), + Role: strPtr("bob"), + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("password"), + Arg: &ast.String{Str: "secret"}, + }, + }, + }, + }, + }, + }, + }, + { + stmt: `CREATE USER charlie LOGIN`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.CreateRoleStmt{ + StmtType: ast.RoleStmtType(2), + Role: strPtr("charlie"), + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("login"), + Arg: &ast.Boolean{Boolval: true}, + }, + }, + }, + }, + }, + }, + }, + { + stmt: `CREATE USER dave NOLOGIN`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.CreateRoleStmt{ + StmtType: ast.RoleStmtType(2), + Role: strPtr("dave"), + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("nologin"), + Arg: &ast.Boolean{Boolval: false}, + }, + }, + }, + }, + }, + }, + }, + { + stmt: `CREATE USER bjorn HASH 'abc123'`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.CreateRoleStmt{ + StmtType: ast.RoleStmtType(2), + Role: strPtr("bjorn"), + Options: &ast.List{ + Items: []ast.Node{ + &ast.DefElem{ + Defname: strPtr("hash"), + Arg: &ast.String{Str: "abc123"}, + }, + }, + }, + }, + }, + }, + }, + } + + p := ydb.NewParser() + for _, tc := range tests { + t.Run(tc.stmt, func(t *testing.T) { + stmts, err := p.Parse(strings.NewReader(tc.stmt)) + if err != nil { + t.Fatalf("Ошибка парсинга запроса %q: %v", tc.stmt, err) + } + if len(stmts) == 0 { + t.Fatalf("Запрос %q не распарсен", tc.stmt) + } + + diff := cmp.Diff(tc.expected, &stmts[0], + cmpopts.IgnoreFields(ast.RawStmt{}, "StmtLocation", "StmtLen"), + cmpopts.IgnoreFields(ast.A_Const{}, "Location"), + cmpopts.IgnoreFields(ast.DefElem{}, "Location"), + ) + if diff != "" { + t.Errorf("Несовпадение AST (-ожидалось +получено):\n%s", diff) + } + }) + } +} diff --git a/internal/engine/ydb/catalog_tests/delete_test.go b/internal/engine/ydb/catalog_tests/delete_test.go new file mode 100644 index 0000000000..b75591a9ef --- /dev/null +++ b/internal/engine/ydb/catalog_tests/delete_test.go @@ -0,0 +1,200 @@ +package ydb_test + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func TestDelete(t *testing.T) { + tests := []struct { + stmt string + expected ast.Node + }{ + { + stmt: "DELETE FROM users WHERE id = 1 RETURNING id", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.DeleteStmt{ + Relations: &ast.List{ + Items: []ast.Node{ + &ast.RangeVar{Relname: strPtr("users")}, + }, + }, + WhereClause: &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: "="}}}, + Lexpr: &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{&ast.String{Str: "id"}}}, + }, + Rexpr: &ast.A_Const{ + Val: &ast.Integer{Ival: 1}, + }, + }, + ReturningList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{&ast.String{Str: "id"}}, + }, + }, + }, + }, + }, + Batch: false, + OnCols: nil, + OnSelectStmt: nil, + }, + }, + }, + }, + { + stmt: "BATCH DELETE FROM users WHERE is_deleted = true RETURNING *", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.DeleteStmt{ + Relations: &ast.List{ + Items: []ast.Node{ + &ast.RangeVar{Relname: strPtr("users")}, + }, + }, + WhereClause: &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: "="}}}, + Lexpr: &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{&ast.String{Str: "is_deleted"}}}, + }, + Rexpr: &ast.A_Const{ + Val: &ast.Boolean{Boolval: true}, + }, + }, + ReturningList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{&ast.A_Star{}}}, + }, + }, + }, + }, + Batch: true, + OnCols: nil, + OnSelectStmt: nil, + }, + }, + }, + }, + { + stmt: "DELETE FROM users ON (id) VALUES (5) RETURNING id", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.DeleteStmt{ + Relations: &ast.List{Items: []ast.Node{&ast.RangeVar{Relname: strPtr("users")}}}, + OnCols: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{Name: strPtr("id")}, + }, + }, + OnSelectStmt: &ast.SelectStmt{ + ValuesLists: &ast.List{ + Items: []ast.Node{ + &ast.List{ + Items: []ast.Node{ + &ast.A_Const{Val: &ast.Integer{Ival: 5}}, + }, + }, + }, + }, + FromClause: &ast.List{}, + TargetList: &ast.List{}, + }, + ReturningList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{ + &ast.String{Str: "id"}, + }, + }, + }, + }, + }, + }, + Batch: false, + WhereClause: nil, + }, + }, + }, + }, + { + stmt: "DELETE FROM users ON (id) SELECT 1 AS id RETURNING id", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.DeleteStmt{ + Relations: &ast.List{Items: []ast.Node{&ast.RangeVar{Relname: strPtr("users")}}}, + OnCols: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{Name: strPtr("id")}, + }, + }, + OnSelectStmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Name: strPtr("id"), + Val: &ast.A_Const{Val: &ast.Integer{Ival: 1}}, + }, + }, + }, + FromClause: &ast.List{}, + }, + ReturningList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{&ast.String{Str: "id"}}}, + }, + }, + }, + }, + Batch: false, + WhereClause: nil, + }, + }, + }, + }, + } + + p := ydb.NewParser() + for _, tc := range tests { + t.Run(tc.stmt, func(t *testing.T) { + stmts, err := p.Parse(strings.NewReader(tc.stmt)) + if err != nil { + t.Fatalf("Ошибка парсинга запроса %q: %v", tc.stmt, err) + } + if len(stmts) == 0 { + t.Fatalf("Запрос %q не распарсен", tc.stmt) + } + + diff := cmp.Diff(tc.expected, &stmts[0], + cmpopts.IgnoreFields(ast.RawStmt{}, "StmtLocation", "StmtLen"), + cmpopts.IgnoreFields(ast.A_Const{}, "Location"), + cmpopts.IgnoreFields(ast.ResTarget{}, "Location"), + cmpopts.IgnoreFields(ast.ColumnRef{}, "Location"), + cmpopts.IgnoreFields(ast.A_Expr{}, "Location"), + cmpopts.IgnoreFields(ast.RangeVar{}, "Location"), + ) + if diff != "" { + t.Errorf("Несовпадение AST (-ожидалось +получено):\n%s", diff) + } + }) + } +} diff --git a/internal/engine/ydb/catalog_tests/drop_role_test.go b/internal/engine/ydb/catalog_tests/drop_role_test.go new file mode 100644 index 0000000000..1d7c6a7658 --- /dev/null +++ b/internal/engine/ydb/catalog_tests/drop_role_test.go @@ -0,0 +1,87 @@ +package ydb_test + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func TestDropRole(t *testing.T) { + tests := []struct { + stmt string + expected ast.Node + }{ + { + stmt: `DROP USER user1;`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.DropRoleStmt{ + MissingOk: false, + Roles: &ast.List{ + Items: []ast.Node{ + &ast.RoleSpec{Rolename: strPtr("user1"), Roletype: ast.RoleSpecType(1)}, + }, + }, + }, + }, + }, + }, + { + stmt: "DROP USER IF EXISTS admin, user2", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.DropRoleStmt{ + MissingOk: true, + Roles: &ast.List{ + Items: []ast.Node{ + &ast.RoleSpec{Rolename: strPtr("admin"), Roletype: ast.RoleSpecType(1)}, + &ast.RoleSpec{Rolename: strPtr("user2"), Roletype: ast.RoleSpecType(1)}, + }, + }, + }, + }, + }, + }, + { + stmt: "DROP GROUP team1, team2", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.DropRoleStmt{ + MissingOk: false, + Roles: &ast.List{ + Items: []ast.Node{ + &ast.RoleSpec{Rolename: strPtr("team1"), Roletype: ast.RoleSpecType(1)}, + &ast.RoleSpec{Rolename: strPtr("team2"), Roletype: ast.RoleSpecType(1)}, + }, + }, + }, + }, + }, + }, + } + + p := ydb.NewParser() + for _, tc := range tests { + t.Run(tc.stmt, func(t *testing.T) { + stmts, err := p.Parse(strings.NewReader(tc.stmt)) + if err != nil { + t.Fatalf("Error parsing %q: %v", tc.stmt, err) + } + if len(stmts) == 0 { + t.Fatalf("Statement %q was not parsed", tc.stmt) + } + + diff := cmp.Diff(tc.expected, &stmts[0], + cmpopts.IgnoreFields(ast.RawStmt{}, "StmtLocation", "StmtLen"), + cmpopts.IgnoreFields(ast.RoleSpec{}, "Location"), + ) + if diff != "" { + t.Errorf("AST mismatch for %q (-expected +got):\n%s", tc.stmt, diff) + } + }) + } +} diff --git a/internal/engine/ydb/catalog_tests/insert_test.go b/internal/engine/ydb/catalog_tests/insert_test.go new file mode 100644 index 0000000000..40f116a3ba --- /dev/null +++ b/internal/engine/ydb/catalog_tests/insert_test.go @@ -0,0 +1,142 @@ +package ydb_test + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func TestInsert(t *testing.T) { + tests := []struct { + stmt string + expected ast.Node + }{ + { + stmt: "INSERT INTO users (id, name) VALUES (1, 'Alice') RETURNING *", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.InsertStmt{ + Relation: &ast.RangeVar{Relname: strPtr("users")}, + Cols: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{Name: strPtr("id")}, + &ast.ResTarget{Name: strPtr("name")}, + }, + }, + SelectStmt: &ast.SelectStmt{ + ValuesLists: &ast.List{ + Items: []ast.Node{ + &ast.List{ + Items: []ast.Node{ + &ast.A_Const{Val: &ast.Integer{Ival: 1}}, + &ast.A_Const{Val: &ast.String{Str: "Alice"}}, + }, + }, + }, + }, + TargetList: &ast.List{}, + FromClause: &ast.List{}, + }, + OnConflictClause: &ast.OnConflictClause{}, + ReturningList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{&ast.A_Star{}}}, + }, + }, + }, + }, + }, + }, + }, + }, + { + stmt: "INSERT OR IGNORE INTO users (id) VALUES (3) RETURNING id, name", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.InsertStmt{ + Relation: &ast.RangeVar{Relname: strPtr("users")}, + Cols: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{Name: strPtr("id")}, + }, + }, + SelectStmt: &ast.SelectStmt{ + ValuesLists: &ast.List{ + Items: []ast.Node{ + &ast.List{ + Items: []ast.Node{ + &ast.A_Const{Val: &ast.Integer{Ival: 3}}, + }, + }, + }, + }, + TargetList: &ast.List{}, + FromClause: &ast.List{}, + }, + OnConflictClause: &ast.OnConflictClause{ + Action: ast.OnConflictAction_INSERT_OR_IGNORE, + }, + ReturningList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{Fields: &ast.List{Items: []ast.Node{&ast.String{Str: "id"}}}}, + }, + &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{Fields: &ast.List{Items: []ast.Node{&ast.String{Str: "name"}}}}, + }, + }, + }, + }, + }, + }, + }, + { + stmt: "UPSERT INTO users (id) VALUES (4) RETURNING id", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.InsertStmt{ + Relation: &ast.RangeVar{Relname: strPtr("users")}, + Cols: &ast.List{Items: []ast.Node{&ast.ResTarget{Name: strPtr("id")}}}, + SelectStmt: &ast.SelectStmt{ValuesLists: &ast.List{Items: []ast.Node{&ast.List{Items: []ast.Node{&ast.A_Const{Val: &ast.Integer{Ival: 4}}}}}}, TargetList: &ast.List{}, FromClause: &ast.List{}}, + OnConflictClause: &ast.OnConflictClause{Action: ast.OnConflictAction_UPSERT}, + ReturningList: &ast.List{Items: []ast.Node{&ast.ResTarget{Val: &ast.ColumnRef{Fields: &ast.List{Items: []ast.Node{&ast.String{Str: "id"}}}}, Indirection: &ast.List{}}}}, + }, + }, + }, + }, + } + + p := ydb.NewParser() + for _, tc := range tests { + t.Run(tc.stmt, func(t *testing.T) { + stmts, err := p.Parse(strings.NewReader(tc.stmt)) + if err != nil { + t.Fatalf("Ошибка парсинга запроса %q: %v", tc.stmt, err) + } + if len(stmts) == 0 { + t.Fatalf("Запрос %q не распарсен", tc.stmt) + } + + diff := cmp.Diff(tc.expected, &stmts[0], + cmpopts.IgnoreFields(ast.RawStmt{}, "StmtLocation", "StmtLen"), + cmpopts.IgnoreFields(ast.A_Const{}, "Location"), + cmpopts.IgnoreFields(ast.ResTarget{}, "Location"), + cmpopts.IgnoreFields(ast.ColumnRef{}, "Location"), + cmpopts.IgnoreFields(ast.A_Expr{}, "Location"), + cmpopts.IgnoreFields(ast.RangeVar{}, "Location"), + ) + if diff != "" { + t.Errorf("Несовпадение AST (-ожидалось +получено):\n%s", diff) + } + }) + } +} diff --git a/internal/engine/ydb/catalog_tests/pragma_test.go b/internal/engine/ydb/catalog_tests/pragma_test.go new file mode 100644 index 0000000000..9db4406c53 --- /dev/null +++ b/internal/engine/ydb/catalog_tests/pragma_test.go @@ -0,0 +1,118 @@ +package ydb_test + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func TestPragma(t *testing.T) { + tests := []struct { + stmt string + expected ast.Node + }{ + { + stmt: `PRAGMA AutoCommit`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.Pragma_stmt{ + Name: &ast.List{ + Items: []ast.Node{ + &ast.A_Const{Val: &ast.String{Str: "autocommit"}}, + }, + }, + }, + }, + }, + }, + { + stmt: `PRAGMA TablePathPrefix = "home/yql"`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.Pragma_stmt{ + Name: &ast.List{ + Items: []ast.Node{ + &ast.A_Const{Val: &ast.String{Str: "tablepathprefix"}}, + }, + }, + Equals: true, + Values: &ast.List{ + Items: []ast.Node{ + &ast.A_Const{Val: &ast.String{Str: "home/yql"}}, + }, + }, + }, + }, + }, + }, + { + stmt: `PRAGMA Warning("disable", "1101")`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.Pragma_stmt{ + Name: &ast.List{ + Items: []ast.Node{ + &ast.A_Const{Val: &ast.String{Str: "warning"}}, + }, + }, + Equals: false, + Values: &ast.List{ + Items: []ast.Node{ + &ast.A_Const{Val: &ast.String{Str: "disable"}}, + &ast.A_Const{Val: &ast.String{Str: "1101"}}, + }, + }, + }, + }, + }, + }, + { + stmt: `PRAGMA yson.AutoConvert = true`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.Pragma_stmt{ + Name: &ast.List{ + Items: []ast.Node{ + &ast.A_Const{Val: &ast.String{Str: "yson"}}, + &ast.A_Const{Val: &ast.String{Str: "autoconvert"}}, + }, + }, + Equals: true, + Values: &ast.List{ + Items: []ast.Node{ + &ast.A_Const{Val: &ast.Boolean{Boolval: true}}, + }, + }, + }, + }, + }, + }, + } + + p := ydb.NewParser() + for _, tc := range tests { + t.Run(tc.stmt, func(t *testing.T) { + stmts, err := p.Parse(strings.NewReader(tc.stmt)) + if err != nil { + t.Fatalf("Ошибка парсинга запроса %q: %v", tc.stmt, err) + } + if len(stmts) == 0 { + t.Fatalf("Запрос %q не распарсен", tc.stmt) + } + + diff := cmp.Diff(tc.expected, &stmts[0], + cmpopts.IgnoreFields(ast.RawStmt{}, "StmtLocation", "StmtLen"), + cmpopts.IgnoreFields(ast.Pragma_stmt{}, "Location"), + cmpopts.IgnoreFields(ast.ColumnRef{}, "Location"), + cmpopts.IgnoreFields(ast.A_Const{}, "Location"), + ) + if diff != "" { + t.Errorf("Несовпадение AST (-ожидалось +получено):\n%s", diff) + } + }) + } +} diff --git a/internal/engine/ydb/catalog_tests/select_test.go b/internal/engine/ydb/catalog_tests/select_test.go new file mode 100644 index 0000000000..47794ce81f --- /dev/null +++ b/internal/engine/ydb/catalog_tests/select_test.go @@ -0,0 +1,397 @@ +package ydb_test + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func strPtr(s string) *string { + return &s +} + +func TestSelect(t *testing.T) { + tests := []struct { + stmt string + expected ast.Node + }{ + // Basic Types Select + { + stmt: `SELECT 52`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Val: &ast.A_Const{ + Val: &ast.Integer{Ival: 52}, + }, + }, + }, + }, + FromClause: &ast.List{}, + }, + }, + }, + }, + { + stmt: `SELECT 'hello'`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Val: &ast.A_Const{ + Val: &ast.String{Str: "hello"}, + }, + }, + }, + }, + FromClause: &ast.List{}, + }, + }, + }, + }, + { + stmt: `SELECT 'it\'s string with quote in it'`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Val: &ast.A_Const{ + Val: &ast.String{Str: `it\'s string with quote in it`}, + }, + }, + }, + }, + FromClause: &ast.List{}, + }, + }, + }, + }, + { + stmt: "SELECT 3.14", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Val: &ast.A_Const{ + Val: &ast.Float{Str: "3.14"}, + }, + }, + }, + }, + FromClause: &ast.List{}, + }, + }, + }, + }, + { + stmt: "SELECT NULL", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Val: &ast.Null{}, + }, + }, + }, + FromClause: &ast.List{}, + }, + }, + }, + }, + { + stmt: "SELECT true", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Val: &ast.A_Const{ + Val: &ast.Boolean{Boolval: true}, + }, + }, + }, + }, + FromClause: &ast.List{}, + }, + }, + }, + }, + { + stmt: "SELECT 2+3*4", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Val: &ast.A_Expr{ + Name: &ast.List{ + Items: []ast.Node{ + &ast.String{Str: "+"}, + }, + }, + Lexpr: &ast.A_Const{ + Val: &ast.Integer{Ival: 2}, + }, + Rexpr: &ast.A_Expr{ + Name: &ast.List{ + Items: []ast.Node{ + &ast.String{Str: "*"}, + }, + }, + Lexpr: &ast.A_Const{ + Val: &ast.Integer{Ival: 3}, + }, + Rexpr: &ast.A_Const{ + Val: &ast.Integer{Ival: 4}, + }, + }, + }, + }, + }, + }, + FromClause: &ast.List{}, + }, + }, + }, + }, + + // Select with From Clause tests + { + stmt: `SELECT * FROM users`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Val: &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{ + &ast.A_Star{}, + }, + }, + }, + }, + }, + }, + FromClause: &ast.List{ + Items: []ast.Node{ + &ast.RangeVar{ + Relname: strPtr("users"), + }, + }, + }, + }, + }, + }, + }, + { + stmt: "SELECT id AS identifier FROM users", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Name: strPtr("identifier"), + Val: &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{ + &ast.String{Str: "id"}, + }, + }, + }, + }, + }, + }, + FromClause: &ast.List{ + Items: []ast.Node{ + &ast.RangeVar{ + Relname: strPtr("users"), + }, + }, + }, + }, + }, + }, + }, + { + stmt: "SELECT a.b.c FROM table", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Val: &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{ + &ast.String{Str: "a"}, + &ast.String{Str: "b"}, + &ast.String{Str: "c"}, + }, + }, + }, + }, + }, + }, + FromClause: &ast.List{ + Items: []ast.Node{ + &ast.RangeVar{ + Relname: strPtr("table"), + }, + }, + }, + }, + }, + }, + }, + { + stmt: "SELECT id.age, 3.14, 'abc', NULL, false FROM users", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Val: &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{ + &ast.String{Str: "id"}, + &ast.String{Str: "age"}, + }, + }, + }, + }, + &ast.ResTarget{ + Val: &ast.A_Const{ + Val: &ast.Float{Str: "3.14"}, + }, + }, + &ast.ResTarget{ + Val: &ast.A_Const{ + Val: &ast.String{Str: "abc"}, + }, + }, + &ast.ResTarget{ + Val: &ast.Null{}, + }, + &ast.ResTarget{ + Val: &ast.A_Const{ + Val: &ast.Boolean{Boolval: false}, + }, + }, + }, + }, + FromClause: &ast.List{ + Items: []ast.Node{ + &ast.RangeVar{ + Relname: strPtr("users"), + }, + }, + }, + }, + }, + }, + }, + { + stmt: `SELECT id, name FROM users WHERE age > 30`, + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.SelectStmt{ + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Val: &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{ + &ast.String{Str: "id"}, + }, + }, + }, + }, + &ast.ResTarget{ + Val: &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{ + &ast.String{Str: "name"}, + }, + }, + }, + }, + }, + }, + FromClause: &ast.List{ + Items: []ast.Node{ + &ast.RangeVar{ + Relname: strPtr("users"), + }, + }, + }, + WhereClause: &ast.A_Expr{ + Name: &ast.List{ + Items: []ast.Node{ + &ast.String{Str: ">"}, + }, + }, + Lexpr: &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{ + &ast.String{Str: "age"}, + }, + }, + }, + Rexpr: &ast.A_Const{ + Val: &ast.Integer{Ival: 30}, + }, + }, + }, + }, + }, + }, + } + + p := ydb.NewParser() + + for _, tc := range tests { + t.Run(tc.stmt, func(t *testing.T) { + stmts, err := p.Parse(strings.NewReader(tc.stmt)) + if err != nil { + t.Fatalf("Ошибка парсинга запроса %q: %v", tc.stmt, err) + } + if len(stmts) == 0 { + t.Fatalf("Запрос %q не распарсен", tc.stmt) + } + + diff := cmp.Diff(tc.expected, &stmts[0], + cmpopts.IgnoreFields(ast.RawStmt{}, "StmtLocation", "StmtLen"), + // cmpopts.IgnoreFields(ast.SelectStmt{}, "Location"), + cmpopts.IgnoreFields(ast.A_Const{}, "Location"), + cmpopts.IgnoreFields(ast.ResTarget{}, "Location"), + cmpopts.IgnoreFields(ast.ColumnRef{}, "Location"), + cmpopts.IgnoreFields(ast.A_Expr{}, "Location"), + cmpopts.IgnoreFields(ast.RangeVar{}, "Location"), + ) + if diff != "" { + t.Errorf("Несовпадение AST (-ожидалось +получено):\n%s", diff) + } + }) + } +} diff --git a/internal/engine/ydb/catalog_tests/update_test.go b/internal/engine/ydb/catalog_tests/update_test.go new file mode 100644 index 0000000000..57c90bc2a4 --- /dev/null +++ b/internal/engine/ydb/catalog_tests/update_test.go @@ -0,0 +1,185 @@ +package ydb_test + +import ( + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/sqlc-dev/sqlc/internal/engine/ydb" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func TestUpdate(t *testing.T) { + tests := []struct { + stmt string + expected ast.Node + }{ + { + stmt: "UPDATE users SET name = 'Bob' WHERE id = 1 RETURNING id;", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.UpdateStmt{ + Relations: &ast.List{ + Items: []ast.Node{ + &ast.RangeVar{Relname: strPtr("users")}, + }, + }, + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Name: strPtr("name"), + Val: &ast.A_Const{ + Val: &ast.String{Str: "Bob"}, + }, + }, + }, + }, + WhereClause: &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: "="}}}, + Lexpr: &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{&ast.String{Str: "id"}}}, + }, + Rexpr: &ast.A_Const{ + Val: &ast.Integer{Ival: 1}, + }, + }, + ReturningList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{&ast.String{Str: "id"}}}, + }, + }, + }, + }, + FromClause: &ast.List{}, + WithClause: nil, + Batch: false, + OnCols: nil, + OnSelectStmt: nil, + }, + }, + }, + }, + { + stmt: "BATCH UPDATE users SET name = 'Charlie' WHERE id = 2 RETURNING *", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.UpdateStmt{ + Relations: &ast.List{ + Items: []ast.Node{ + &ast.RangeVar{Relname: strPtr("users")}, + }, + }, + TargetList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Name: strPtr("name"), + Val: &ast.A_Const{Val: &ast.String{Str: "Charlie"}}, + }, + }, + }, + WhereClause: &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: "="}}}, + Lexpr: &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{&ast.String{Str: "id"}}}, + }, + Rexpr: &ast.A_Const{ + Val: &ast.Integer{Ival: 2}, + }, + }, + ReturningList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{&ast.A_Star{}}}, + }, + }, + }, + }, + FromClause: &ast.List{}, + WithClause: nil, + Batch: true, + OnCols: nil, + OnSelectStmt: nil, + }, + }, + }, + }, + { + stmt: "UPDATE users ON (id) VALUES (5) RETURNING id", + expected: &ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: &ast.UpdateStmt{ + Relations: &ast.List{Items: []ast.Node{&ast.RangeVar{Relname: strPtr("users")}}}, + OnCols: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{Name: strPtr("id")}, + }, + }, + OnSelectStmt: &ast.SelectStmt{ + ValuesLists: &ast.List{ + Items: []ast.Node{ + &ast.List{ + Items: []ast.Node{ + &ast.A_Const{Val: &ast.Integer{Ival: 5}}, + }, + }, + }, + }, + FromClause: &ast.List{}, + TargetList: &ast.List{}, + }, + ReturningList: &ast.List{ + Items: []ast.Node{ + &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{ + &ast.String{Str: "id"}, + }, + }, + }, + }, + }, + }, + FromClause: &ast.List{}, + WithClause: nil, + Batch: false, + TargetList: nil, + WhereClause: nil, + }, + }, + }, + }, + } + + p := ydb.NewParser() + for _, tc := range tests { + t.Run(tc.stmt, func(t *testing.T) { + stmts, err := p.Parse(strings.NewReader(tc.stmt)) + if err != nil { + t.Fatalf("Ошибка парсинга запроса %q: %v", tc.stmt, err) + } + if len(stmts) == 0 { + t.Fatalf("Запрос %q не распарсен", tc.stmt) + } + + diff := cmp.Diff(tc.expected, &stmts[0], + cmpopts.IgnoreFields(ast.RawStmt{}, "StmtLocation", "StmtLen"), + cmpopts.IgnoreFields(ast.A_Const{}, "Location"), + cmpopts.IgnoreFields(ast.ResTarget{}, "Location"), + cmpopts.IgnoreFields(ast.ColumnRef{}, "Location"), + cmpopts.IgnoreFields(ast.A_Expr{}, "Location"), + cmpopts.IgnoreFields(ast.RangeVar{}, "Location"), + ) + if diff != "" { + t.Errorf("Несовпадение AST (-ожидалось +получено):\n%s", diff) + } + }) + } +} diff --git a/internal/engine/ydb/convert.go b/internal/engine/ydb/convert.go new file mode 100755 index 0000000000..b4d9490d0b --- /dev/null +++ b/internal/engine/ydb/convert.go @@ -0,0 +1,2601 @@ +package ydb + +import ( + "log" + "strconv" + "strings" + + "github.com/antlr4-go/antlr/v4" + "github.com/sqlc-dev/sqlc/internal/debug" + "github.com/sqlc-dev/sqlc/internal/sql/ast" + parser "github.com/ydb-platform/yql-parsers/go" +) + +type cc struct { + paramCount int + content string +} + +func (c *cc) pos(token antlr.Token) int { + if token == nil { + return 0 + } + runeIdx := token.GetStart() + return byteOffsetFromRuneIndex(c.content, runeIdx) +} + +type node interface { + GetParser() antlr.Parser +} + +func todo(funcname string, n node) *ast.TODO { + if debug.Active { + log.Printf("ydb.%s: Unknown node type %T\n", funcname, n) + } + return &ast.TODO{} +} + +func identifier(id string) string { + if len(id) >= 2 && id[0] == '"' && id[len(id)-1] == '"' { + unquoted, _ := strconv.Unquote(id) + return unquoted + } + return strings.ToLower(id) +} + +func stripQuotes(s string) string { + if len(s) >= 2 && (s[0] == '\'' || s[0] == '"') && s[0] == s[len(s)-1] { + return s[1 : len(s)-1] + } + return s +} + +func NewIdentifier(t string) *ast.String { + return &ast.String{Str: identifier(t)} +} + +func (c *cc) convertDrop_role_stmtCOntext(n *parser.Drop_role_stmtContext) ast.Node { + if n.DROP() == nil || (n.USER() == nil && n.GROUP() == nil) || len(n.AllRole_name()) == 0 { + return todo("Drop_role_stmtContext", n) + } + + stmt := &ast.DropRoleStmt{ + MissingOk: n.IF() != nil && n.EXISTS() != nil, + Roles: &ast.List{}, + } + + for _, role := range n.AllRole_name() { + member, isParam, _ := c.extractRoleSpec(role, ast.RoleSpecType(1)) + if member == nil { + return todo("Drop_role_stmtContext", n) + } + + if debug.Active && isParam { + log.Printf("YDB does not currently support parameters in the DROP ROLE statement") + } + + stmt.Roles.Items = append(stmt.Roles.Items, member) + } + + return stmt +} + +func (c *cc) convertAlter_group_stmtContext(n *parser.Alter_group_stmtContext) ast.Node { + if n.ALTER() == nil || n.GROUP() == nil || len(n.AllRole_name()) == 0 { + return todo("convertAlter_group_stmtContext", n) + } + role, paramFlag, _ := c.extractRoleSpec(n.Role_name(0), ast.RoleSpecType(1)) + if role == nil { + return todo("convertAlter_group_stmtContext", n) + } + + if debug.Active && paramFlag { + log.Printf("YDB does not currently support parameters in the ALTER GROUP statement") + } + + stmt := &ast.AlterRoleStmt{ + Role: role, + Action: 1, + Options: &ast.List{}, + } + + switch { + case n.RENAME() != nil && n.TO() != nil && len(n.AllRole_name()) > 1: + newName := c.convert(n.Role_name(1)) + action := "rename" + + defElem := &ast.DefElem{ + Defname: &action, + Defaction: ast.DefElemAction(1), + Location: c.pos(n.Role_name(1).GetStart()), + } + + bindFlag := true + switch v := newName.(type) { + case *ast.A_Const: + switch val := v.Val.(type) { + case *ast.String: + bindFlag = false + defElem.Arg = val + case *ast.Boolean: + defElem.Arg = val + default: + return todo("convertAlter_group_stmtContext", n) + } + case *ast.ParamRef, *ast.A_Expr: + defElem.Arg = newName + default: + return todo("convertAlter_group_stmtContext", n) + } + + if debug.Active && !paramFlag && bindFlag { + log.Printf("YDB does not currently support parameters in the ALTER GROUP statement") + } + + stmt.Options.Items = append(stmt.Options.Items, defElem) + + case (n.ADD() != nil || n.DROP() != nil) && len(n.AllRole_name()) > 1: + defname := "rolemembers" + optionList := &ast.List{} + for _, role := range n.AllRole_name()[1:] { + member, isParam, _ := c.extractRoleSpec(role, ast.RoleSpecType(1)) + if member == nil { + return todo("convertAlter_group_stmtContext", n) + } + + if debug.Active && isParam && !paramFlag { + log.Printf("YDB does not currently support parameters in the ALTER GROUP statement") + } + + optionList.Items = append(optionList.Items, member) + } + + var action ast.DefElemAction + if n.ADD() != nil { + action = 3 + } else { + action = 4 + } + + stmt.Options.Items = append(stmt.Options.Items, &ast.DefElem{ + Defname: &defname, + Arg: optionList, + Defaction: action, + Location: c.pos(n.GetStart()), + }) + } + + return stmt +} + +func (c *cc) convertAlter_user_stmtContext(n *parser.Alter_user_stmtContext) ast.Node { + if n.ALTER() == nil || n.USER() == nil || len(n.AllRole_name()) == 0 { + return todo("Alter_user_stmtContext", n) + } + + role, paramFlag, _ := c.extractRoleSpec(n.Role_name(0), ast.RoleSpecType(1)) + if role == nil { + return todo("convertAlter_group_stmtContext", n) + } + + if debug.Active && paramFlag { + log.Printf("YDB does not currently support parameters in the ALTER USER statement") + } + + stmt := &ast.AlterRoleStmt{ + Role: role, + Action: 1, + Options: &ast.List{}, + } + + switch { + case n.RENAME() != nil && n.TO() != nil && len(n.AllRole_name()) > 1: + newName := c.convert(n.Role_name(1)) + action := "rename" + + defElem := &ast.DefElem{ + Defname: &action, + Defaction: ast.DefElemAction(1), + Location: c.pos(n.Role_name(1).GetStart()), + } + + bindFlag := true + switch v := newName.(type) { + case *ast.A_Const: + switch val := v.Val.(type) { + case *ast.String: + bindFlag = false + defElem.Arg = val + case *ast.Boolean: + defElem.Arg = val + default: + return todo("Alter_user_stmtContext", n) + } + case *ast.ParamRef, *ast.A_Expr: + defElem.Arg = newName + default: + return todo("Alter_user_stmtContext", n) + } + + if debug.Active && !paramFlag && bindFlag { + log.Printf("YDB does not currently support parameters in the ALTER USER statement") + } + + stmt.Options.Items = append(stmt.Options.Items, defElem) + + case len(n.AllUser_option()) > 0: + for _, opt := range n.AllUser_option() { + if node := c.convert(opt); node != nil { + stmt.Options.Items = append(stmt.Options.Items, node) + } + } + } + + return stmt +} + +func (c *cc) convertCreate_group_stmtContext(n *parser.Create_group_stmtContext) ast.Node { + if n.CREATE() == nil || n.GROUP() == nil || len(n.AllRole_name()) == 0 { + return todo("Create_group_stmtContext", n) + } + groupName := c.convert(n.Role_name(0)) + + stmt := &ast.CreateRoleStmt{ + StmtType: ast.RoleStmtType(3), + Options: &ast.List{}, + } + + paramFlag := true + switch v := groupName.(type) { + case *ast.A_Const: + switch val := v.Val.(type) { + case *ast.String: + paramFlag = false + stmt.Role = &val.Str + case *ast.Boolean: + stmt.BindRole = groupName + default: + return todo("convertCreate_group_stmtContext", n) + } + case *ast.ParamRef, *ast.A_Expr: + stmt.BindRole = groupName + default: + return todo("convertCreate_group_stmtContext", n) + } + + if debug.Active && paramFlag { + log.Printf("YDB does not currently support parameters in the CREATE GROUP statement") + } + + if n.WITH() != nil && n.USER() != nil && len(n.AllRole_name()) > 1 { + defname := "rolemembers" + optionList := &ast.List{} + for _, role := range n.AllRole_name()[1:] { + member, isParam, _ := c.extractRoleSpec(role, ast.RoleSpecType(1)) + if member == nil { + return todo("convertCreate_group_stmtContext", n) + } + + if debug.Active && isParam && !paramFlag { + log.Printf("YDB does not currently support parameters in the CREATE GROUP statement") + } + + optionList.Items = append(optionList.Items, member) + } + + stmt.Options.Items = append(stmt.Options.Items, &ast.DefElem{ + Defname: &defname, + Arg: optionList, + Location: c.pos(n.GetStart()), + }) + } + + return stmt +} + +func (c *cc) convertUse_stmtContext(n *parser.Use_stmtContext) ast.Node { + if n.USE() != nil && n.Cluster_expr() != nil { + clusterExpr := c.convert(n.Cluster_expr()) + stmt := &ast.UseStmt{ + Xpr: clusterExpr, + Location: c.pos(n.GetStart()), + } + return stmt + } + return todo("convertUse_stmtContext", n) +} + +func (c *cc) convertCluster_exprContext(n *parser.Cluster_exprContext) ast.Node { + var node ast.Node + + switch { + case n.Pure_column_or_named() != nil: + pureCtx := n.Pure_column_or_named() + if anID := pureCtx.An_id(); anID != nil { + name := parseAnId(anID) + node = &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{NewIdentifier(name)}}, + Location: c.pos(anID.GetStart()), + } + } else if bp := pureCtx.Bind_parameter(); bp != nil { + node = c.convert(bp) + } + case n.ASTERISK() != nil: + node = &ast.A_Star{} + default: + return todo("convertCluster_exprContext", n) + } + + if n.An_id() != nil && n.COLON() != nil { + name := parseAnId(n.An_id()) + return &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: ":"}}}, + Lexpr: &ast.String{Str: name}, + Rexpr: node, + Location: c.pos(n.GetStart()), + } + } + + return node +} + +func (c *cc) convertCreate_user_stmtContext(n *parser.Create_user_stmtContext) ast.Node { + if n.CREATE() == nil || n.USER() == nil || n.Role_name() == nil { + return todo("convertCreate_user_stmtContext", n) + } + roleNode := c.convert(n.Role_name()) + + stmt := &ast.CreateRoleStmt{ + StmtType: ast.RoleStmtType(2), + Options: &ast.List{}, + } + + paramFlag := true + switch v := roleNode.(type) { + case *ast.A_Const: + switch val := v.Val.(type) { + case *ast.String: + paramFlag = false + stmt.Role = &val.Str + case *ast.Boolean: + stmt.BindRole = roleNode + default: + return todo("convertCreate_user_stmtContext", n) + } + case *ast.ParamRef, *ast.A_Expr: + stmt.BindRole = roleNode + default: + return todo("convertCreate_user_stmtContext", n) + } + + if debug.Active && paramFlag { + log.Printf("YDB does not currently support parameters in the CREATE USER statement") + } + + if len(n.AllUser_option()) > 0 { + options := []ast.Node{} + for _, opt := range n.AllUser_option() { + if node := c.convert(opt); node != nil { + options = append(options, node) + } + } + if len(options) > 0 { + stmt.Options = &ast.List{Items: options} + } + } + return stmt +} + +func (c *cc) convertUser_optionContext(n *parser.User_optionContext) ast.Node { + switch { + case n.Authentication_option() != nil: + aOpt := n.Authentication_option() + if pOpt := aOpt.Password_option(); pOpt != nil { + if pOpt.PASSWORD() != nil { + name := "password" + pValue := pOpt.Password_value() + var password ast.Node + if pValue.STRING_VALUE() != nil { + password = &ast.String{Str: stripQuotes(pValue.STRING_VALUE().GetText())} + } else { + password = &ast.Null{} + } + return &ast.DefElem{ + Defname: &name, + Arg: password, + Location: c.pos(pOpt.GetStart()), + } + } + } else if hOpt := aOpt.Hash_option(); hOpt != nil { + if debug.Active { + log.Printf("YDB does not currently support HASH in CREATE USER statement") + } + var pass string + if hOpt.HASH() != nil && hOpt.STRING_VALUE() != nil { + pass = stripQuotes(hOpt.STRING_VALUE().GetText()) + } + name := "hash" + return &ast.DefElem{ + Defname: &name, + Arg: &ast.String{Str: pass}, + Location: c.pos(hOpt.GetStart()), + } + } + + case n.Login_option() != nil: + lOpt := n.Login_option() + var name string + if lOpt.LOGIN() != nil { + name = "login" + } else if lOpt.NOLOGIN() != nil { + name = "nologin" + } + return &ast.DefElem{ + Defname: &name, + Arg: &ast.Boolean{Boolval: lOpt.LOGIN() != nil}, + Location: c.pos(lOpt.GetStart()), + } + default: + return todo("convertUser_optionContext", n) + } + return nil +} + +func (c *cc) convertRole_nameContext(n *parser.Role_nameContext) ast.Node { + switch { + case n.An_id_or_type() != nil: + name := parseAnIdOrType(n.An_id_or_type()) + return &ast.A_Const{Val: NewIdentifier(name), Location: c.pos(n.GetStart())} + case n.Bind_parameter() != nil: + bindPar := c.convert(n.Bind_parameter()) + return bindPar + } + return todo("convertRole_nameContext", n) +} + +func (c *cc) convertCommit_stmtContext(n *parser.Commit_stmtContext) ast.Node { + if n.COMMIT() != nil { + return &ast.TransactionStmt{Kind: ast.TransactionStmtKind(3)} + } + return todo("convertCommit_stmtContext", n) +} + +func (c *cc) convertRollback_stmtContext(n *parser.Rollback_stmtContext) ast.Node { + if n.ROLLBACK() != nil { + return &ast.TransactionStmt{Kind: ast.TransactionStmtKind(4)} + } + return todo("convertRollback_stmtContext", n) +} + +func (c *cc) convertDrop_table_stmtContext(n *parser.Drop_table_stmtContext) ast.Node { + if n.DROP() != nil && (n.TABLESTORE() != nil || (n.EXTERNAL() != nil && n.TABLE() != nil) || n.TABLE() != nil) { + name := parseTableName(n.Simple_table_ref().Simple_table_ref_core()) + stmt := &ast.DropTableStmt{ + IfExists: n.IF() != nil && n.EXISTS() != nil, + Tables: []*ast.TableName{name}, + } + return stmt + } + return todo("convertDrop_Table_stmtContxt", n) +} + +func (c *cc) convertDelete_stmtContext(n *parser.Delete_stmtContext) ast.Node { + batch := n.BATCH() != nil + + tableName := identifier(n.Simple_table_ref().Simple_table_ref_core().GetText()) + rel := &ast.RangeVar{Relname: &tableName} + + var where ast.Node + if n.WHERE() != nil && n.Expr() != nil { + where = c.convert(n.Expr()) + } + var cols *ast.List + var source ast.Node + if n.ON() != nil && n.Into_values_source() != nil { + nVal := n.Into_values_source() + // todo: handle default values when implemented + if pureCols := nVal.Pure_column_list(); pureCols != nil { + cols = &ast.List{} + for _, anID := range pureCols.AllAn_id() { + name := identifier(parseAnId(anID)) + cols.Items = append(cols.Items, &ast.ResTarget{ + Name: &name, + Location: c.pos(anID.GetStart()), + }) + } + } + + valSource := nVal.Values_source() + if valSource != nil { + switch { + case valSource.Values_stmt() != nil: + source = &ast.SelectStmt{ + ValuesLists: c.convert(valSource.Values_stmt()).(*ast.List), + FromClause: &ast.List{}, + TargetList: &ast.List{}, + } + + case valSource.Select_stmt() != nil: + source = c.convert(valSource.Select_stmt()) + } + } + } + + returning := &ast.List{} + if ret := n.Returning_columns_list(); ret != nil { + returning = c.convert(ret).(*ast.List) + } + + stmts := &ast.DeleteStmt{ + Relations: &ast.List{Items: []ast.Node{rel}}, + WhereClause: where, + ReturningList: returning, + Batch: batch, + OnCols: cols, + OnSelectStmt: source, + } + + return stmts +} + +func (c *cc) convertPragma_stmtContext(n *parser.Pragma_stmtContext) ast.Node { + if n.PRAGMA() != nil && n.An_id() != nil { + prefix := "" + if p := n.Opt_id_prefix_or_type(); p != nil { + prefix = parseAnIdOrType(p.An_id_or_type()) + } + items := []ast.Node{} + if prefix != "" { + items = append(items, &ast.A_Const{Val: NewIdentifier(prefix)}) + } + + name := parseAnId(n.An_id()) + items = append(items, &ast.A_Const{Val: NewIdentifier(name)}) + + stmt := &ast.Pragma_stmt{ + Name: &ast.List{Items: items}, + Location: c.pos(n.An_id().GetStart()), + } + + if n.EQUALS() != nil { + stmt.Equals = true + if val := n.Pragma_value(0); val != nil { + stmt.Values = &ast.List{Items: []ast.Node{c.convert(val)}} + } + } else if lp := n.LPAREN(); lp != nil { + values := []ast.Node{} + for _, v := range n.AllPragma_value() { + values = append(values, c.convert(v)) + } + stmt.Values = &ast.List{Items: values} + } + + return stmt + } + return todo("convertPragma_stmtContext", n) +} + +func (c *cc) convertPragma_valueContext(n *parser.Pragma_valueContext) ast.Node { + switch { + case n.Signed_number() != nil: + if n.Signed_number().Integer() != nil { + text := n.Signed_number().GetText() + val, err := parseIntegerValue(text) + if err != nil { + if debug.Active { + log.Printf("Failed to parse integer value '%s': %v", text, err) + } + return &ast.TODO{} + } + return &ast.A_Const{Val: &ast.Integer{Ival: val}, Location: c.pos(n.GetStart())} + } + if n.Signed_number().Real_() != nil { + text := n.Signed_number().GetText() + return &ast.A_Const{Val: &ast.Float{Str: text}, Location: c.pos(n.GetStart())} + } + case n.STRING_VALUE() != nil: + val := n.STRING_VALUE().GetText() + if len(val) >= 2 { + val = val[1 : len(val)-1] + } + return &ast.A_Const{Val: &ast.String{Str: val}, Location: c.pos(n.GetStart())} + case n.Bool_value() != nil: + var i bool + if n.Bool_value().TRUE() != nil { + i = true + } + return &ast.A_Const{Val: &ast.Boolean{Boolval: i}, Location: c.pos(n.GetStart())} + case n.Bind_parameter() != nil: + bindPar := c.convert(n.Bind_parameter()) + return bindPar + } + + return todo("convertPragma_valueContext", n) +} + +func (c *cc) convertUpdate_stmtContext(n *parser.Update_stmtContext) ast.Node { + if n.UPDATE() == nil { + return nil + } + batch := n.BATCH() != nil + + tableName := identifier(n.Simple_table_ref().Simple_table_ref_core().GetText()) + rel := &ast.RangeVar{Relname: &tableName} + + var where ast.Node + var setList *ast.List + var cols *ast.List + var source ast.Node + + if n.SET() != nil && n.Set_clause_choice() != nil { + nSet := n.Set_clause_choice() + setList = &ast.List{Items: []ast.Node{}} + + switch { + case nSet.Set_clause_list() != nil: + for _, clause := range nSet.Set_clause_list().AllSet_clause() { + targetCtx := clause.Set_target() + columnName := identifier(targetCtx.Column_name().GetText()) + expr := c.convert(clause.Expr()) + resTarget := &ast.ResTarget{ + Name: &columnName, + Val: expr, + Location: c.pos(clause.Expr().GetStart()), + } + setList.Items = append(setList.Items, resTarget) + } + + case nSet.Multiple_column_assignment() != nil: + multiAssign := nSet.Multiple_column_assignment() + targetsCtx := multiAssign.Set_target_list() + valuesCtx := multiAssign.Simple_values_source() + + var colNames []string + for _, target := range targetsCtx.AllSet_target() { + targetCtx := target.(*parser.Set_targetContext) + colNames = append(colNames, targetCtx.Column_name().GetText()) + } + + var rowExpr *ast.RowExpr + if exprList := valuesCtx.Expr_list(); exprList != nil { + rowExpr = &ast.RowExpr{ + Args: &ast.List{}, + } + for _, expr := range exprList.AllExpr() { + rowExpr.Args.Items = append(rowExpr.Args.Items, c.convert(expr)) + } + } + + for i, colName := range colNames { + name := identifier(colName) + setList.Items = append(setList.Items, &ast.ResTarget{ + Name: &name, + Val: &ast.MultiAssignRef{ + Source: rowExpr, + Colno: i + 1, + Ncolumns: len(colNames), + }, + Location: c.pos(targetsCtx.Set_target(i).GetStart()), + }) + } + } + + if n.WHERE() != nil && n.Expr() != nil { + where = c.convert(n.Expr()) + } + } else if n.ON() != nil && n.Into_values_source() != nil { + + // todo: handle default values when implemented + + nVal := n.Into_values_source() + + if pureCols := nVal.Pure_column_list(); pureCols != nil { + cols = &ast.List{} + for _, anID := range pureCols.AllAn_id() { + name := identifier(parseAnId(anID)) + cols.Items = append(cols.Items, &ast.ResTarget{ + Name: &name, + Location: c.pos(anID.GetStart()), + }) + } + } + + valSource := nVal.Values_source() + if valSource != nil { + switch { + case valSource.Values_stmt() != nil: + source = &ast.SelectStmt{ + ValuesLists: c.convert(valSource.Values_stmt()).(*ast.List), + FromClause: &ast.List{}, + TargetList: &ast.List{}, + } + + case valSource.Select_stmt() != nil: + source = c.convert(valSource.Select_stmt()) + } + } + } + + returning := &ast.List{} + if ret := n.Returning_columns_list(); ret != nil { + returning = c.convert(ret).(*ast.List) + } + + stmts := &ast.UpdateStmt{ + Relations: &ast.List{Items: []ast.Node{rel}}, + TargetList: setList, + WhereClause: where, + ReturningList: returning, + FromClause: &ast.List{}, + WithClause: nil, + Batch: batch, + OnCols: cols, + OnSelectStmt: source, + } + + return stmts +} + +func (c *cc) convertInto_table_stmtContext(n *parser.Into_table_stmtContext) ast.Node { + tableName := identifier(n.Into_simple_table_ref().Simple_table_ref().Simple_table_ref_core().GetText()) + rel := &ast.RangeVar{ + Relname: &tableName, + Location: c.pos(n.Into_simple_table_ref().GetStart()), + } + + onConflict := &ast.OnConflictClause{} + switch { + case n.INSERT() != nil && n.OR() != nil && n.ABORT() != nil: + onConflict.Action = ast.OnConflictAction_INSERT_OR_ABORT + case n.INSERT() != nil && n.OR() != nil && n.REVERT() != nil: + onConflict.Action = ast.OnConflictAction_INSERT_OR_REVERT + case n.INSERT() != nil && n.OR() != nil && n.IGNORE() != nil: + onConflict.Action = ast.OnConflictAction_INSERT_OR_IGNORE + case n.UPSERT() != nil: + onConflict.Action = ast.OnConflictAction_UPSERT + case n.REPLACE() != nil: + onConflict.Action = ast.OnConflictAction_REPLACE + } + + var cols *ast.List + var source ast.Node + if nVal := n.Into_values_source(); nVal != nil { + // todo: handle default values when implemented + + if pureCols := nVal.Pure_column_list(); pureCols != nil { + cols = &ast.List{} + for _, anID := range pureCols.AllAn_id() { + name := identifier(parseAnId(anID)) + cols.Items = append(cols.Items, &ast.ResTarget{ + Name: &name, + Location: c.pos(anID.GetStart()), + }) + } + } + + valSource := nVal.Values_source() + if valSource != nil { + switch { + case valSource.Values_stmt() != nil: + source = &ast.SelectStmt{ + ValuesLists: c.convert(valSource.Values_stmt()).(*ast.List), + FromClause: &ast.List{}, + TargetList: &ast.List{}, + } + + case valSource.Select_stmt() != nil: + source = c.convert(valSource.Select_stmt()) + } + } + } + + returning := &ast.List{} + if ret := n.Returning_columns_list(); ret != nil { + returning = c.convert(ret).(*ast.List) + } + + stmts := &ast.InsertStmt{ + Relation: rel, + Cols: cols, + SelectStmt: source, + OnConflictClause: onConflict, + ReturningList: returning, + } + + return stmts +} + +func (c *cc) convertValues_stmtContext(n *parser.Values_stmtContext) ast.Node { + mainList := &ast.List{} + + for _, rowCtx := range n.Values_source_row_list().AllValues_source_row() { + rowList := &ast.List{} + exprListCtx := rowCtx.Expr_list().(*parser.Expr_listContext) + + for _, exprCtx := range exprListCtx.AllExpr() { + if converted := c.convert(exprCtx); converted != nil { + rowList.Items = append(rowList.Items, converted) + } + } + + mainList.Items = append(mainList.Items, rowList) + + } + + return mainList +} + +func (c *cc) convertReturning_columns_listContext(n *parser.Returning_columns_listContext) ast.Node { + list := &ast.List{Items: []ast.Node{}} + + if n.ASTERISK() != nil { + target := &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{&ast.A_Star{}}}, + Location: c.pos(n.ASTERISK().GetSymbol()), + }, + Location: c.pos(n.ASTERISK().GetSymbol()), + } + list.Items = append(list.Items, target) + return list + } + + for _, idCtx := range n.AllAn_id() { + target := &ast.ResTarget{ + Indirection: &ast.List{}, + Val: &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{NewIdentifier(parseAnId(idCtx))}, + }, + Location: c.pos(idCtx.GetStart()), + }, + Location: c.pos(idCtx.GetStart()), + } + list.Items = append(list.Items, target) + } + + return list +} + +func (c *cc) convertSelectStmtContext(n *parser.Select_stmtContext) ast.Node { + skp := n.Select_kind_parenthesis(0) + if skp == nil { + return nil + } + partial := skp.Select_kind_partial() + if partial == nil { + return nil + } + sk := partial.Select_kind() + if sk == nil { + return nil + } + selectStmt := &ast.SelectStmt{} + + switch { + case sk.Process_core() != nil: + cnode := c.convert(sk.Process_core()) + stmt, ok := cnode.(*ast.SelectStmt) + if !ok { + return nil + } + selectStmt = stmt + case sk.Select_core() != nil: + cnode := c.convert(sk.Select_core()) + stmt, ok := cnode.(*ast.SelectStmt) + if !ok { + return nil + } + selectStmt = stmt + case sk.Reduce_core() != nil: + cnode := c.convert(sk.Reduce_core()) + stmt, ok := cnode.(*ast.SelectStmt) + if !ok { + return nil + } + selectStmt = stmt + } + + // todo: cover process and reduce core, + // todo: cover LIMIT and OFFSET + + return selectStmt +} + +func (c *cc) convertSelectCoreContext(n *parser.Select_coreContext) ast.Node { + stmt := &ast.SelectStmt{ + TargetList: &ast.List{}, + FromClause: &ast.List{}, + } + if n.Opt_set_quantifier() != nil { + oq := n.Opt_set_quantifier() + if oq.DISTINCT() != nil { + // todo: add distinct support + stmt.DistinctClause = &ast.List{} + } + } + resultCols := n.AllResult_column() + if len(resultCols) > 0 { + var items []ast.Node + for _, rc := range resultCols { + resCol, ok := rc.(*parser.Result_columnContext) + if !ok { + continue + } + convNode := c.convertResultColumn(resCol) + if convNode != nil { + items = append(items, convNode) + } + } + stmt.TargetList = &ast.List{ + Items: items, + } + } + jsList := n.AllJoin_source() + if len(n.AllFROM()) > 0 && len(jsList) > 0 { + var fromItems []ast.Node + for _, js := range jsList { + jsCon, ok := js.(*parser.Join_sourceContext) + if !ok { + continue + } + + joinNode := c.convertJoinSource(jsCon) + if joinNode != nil { + fromItems = append(fromItems, joinNode) + } + } + stmt.FromClause = &ast.List{ + Items: fromItems, + } + } + if n.WHERE() != nil { + whereCtx := n.Expr(0) + if whereCtx != nil { + stmt.WhereClause = c.convert(whereCtx) + } + } + return stmt +} + +func (c *cc) convertResultColumn(n *parser.Result_columnContext) ast.Node { + // todo: support opt_id_prefix + target := &ast.ResTarget{ + Location: c.pos(n.GetStart()), + } + var val ast.Node + iexpr := n.Expr() + switch { + case n.ASTERISK() != nil: + val = c.convertWildCardField(n) + case iexpr != nil: + val = c.convert(iexpr) + } + + if val == nil { + return nil + } + switch { + case n.AS() != nil && n.An_id_or_type() != nil: + name := parseAnIdOrType(n.An_id_or_type()) + target.Name = &name + case n.An_id_as_compat() != nil: //nolint + // todo: parse as_compat + } + target.Val = val + return target +} + +func (c *cc) convertJoinSource(n *parser.Join_sourceContext) ast.Node { + if n == nil { + return nil + } + fsList := n.AllFlatten_source() + if len(fsList) == 0 { + return nil + } + joinOps := n.AllJoin_op() + joinConstraints := n.AllJoin_constraint() + + // todo: add ANY support + + leftNode := c.convertFlattenSource(fsList[0]) + if leftNode == nil { + return nil + } + for i, jopCtx := range joinOps { + if i+1 >= len(fsList) { + break + } + rightNode := c.convertFlattenSource(fsList[i+1]) + if rightNode == nil { + return leftNode + } + jexpr := &ast.JoinExpr{ + Larg: leftNode, + Rarg: rightNode, + } + if jopCtx.NATURAL() != nil { + jexpr.IsNatural = true + } + // todo: cover semi/only/exclusion/ + switch { + case jopCtx.LEFT() != nil: + jexpr.Jointype = ast.JoinTypeLeft + case jopCtx.RIGHT() != nil: + jexpr.Jointype = ast.JoinTypeRight + case jopCtx.FULL() != nil: + jexpr.Jointype = ast.JoinTypeFull + case jopCtx.INNER() != nil: + jexpr.Jointype = ast.JoinTypeInner + case jopCtx.COMMA() != nil: + jexpr.Jointype = ast.JoinTypeInner + default: + jexpr.Jointype = ast.JoinTypeInner + } + if i < len(joinConstraints) { + if jc := joinConstraints[i]; jc != nil { + switch { + case jc.ON() != nil: + if exprCtx := jc.Expr(); exprCtx != nil { + jexpr.Quals = c.convert(exprCtx) + } + case jc.USING() != nil: + if pureListCtx := jc.Pure_column_or_named_list(); pureListCtx != nil { + var using ast.List + pureItems := pureListCtx.AllPure_column_or_named() + for _, pureCtx := range pureItems { + if anID := pureCtx.An_id(); anID != nil { + using.Items = append(using.Items, NewIdentifier(parseAnId(anID))) + } else if bp := pureCtx.Bind_parameter(); bp != nil { + bindPar := c.convert(bp) + using.Items = append(using.Items, bindPar) + } + } + jexpr.UsingClause = &using + } + } + } + } + leftNode = jexpr + } + return leftNode +} + +func (c *cc) convertFlattenSource(n parser.IFlatten_sourceContext) ast.Node { + if n == nil { + return nil + } + nss := n.Named_single_source() + if nss == nil { + return nil + } + namedSingleSource, ok := nss.(*parser.Named_single_sourceContext) + if !ok { + return nil + } + return c.convertNamedSingleSource(namedSingleSource) +} + +func (c *cc) convertNamedSingleSource(n *parser.Named_single_sourceContext) ast.Node { + ss := n.Single_source() + if ss == nil { + return nil + } + SingleSource, ok := ss.(*parser.Single_sourceContext) + if !ok { + return nil + } + base := c.convertSingleSource(SingleSource) + + if n.AS() != nil && n.An_id() != nil { + aliasText := parseAnId(n.An_id()) + switch source := base.(type) { + case *ast.RangeVar: + source.Alias = &ast.Alias{Aliasname: &aliasText} + case *ast.RangeSubselect: + source.Alias = &ast.Alias{Aliasname: &aliasText} + } + } else if n.An_id_as_compat() != nil { //nolint + // todo: parse as_compat + } + return base +} + +func (c *cc) convertSingleSource(n *parser.Single_sourceContext) ast.Node { + if n.Table_ref() != nil { + tableName := n.Table_ref().GetText() // !! debug !! + return &ast.RangeVar{ + Relname: &tableName, + Location: c.pos(n.GetStart()), + } + } + + if n.Select_stmt() != nil { + subquery := c.convert(n.Select_stmt()) + return &ast.RangeSubselect{ + Subquery: subquery, + } + + } + // todo: Values stmt + + return nil +} + +func (c *cc) convertBindParameter(n *parser.Bind_parameterContext) ast.Node { + // !!debug later!! + if n.DOLLAR() != nil { + if n.TRUE() != nil { + return &ast.A_Const{Val: &ast.Boolean{Boolval: true}, Location: c.pos(n.GetStart())} + } + if n.FALSE() != nil { + return &ast.A_Const{Val: &ast.Boolean{Boolval: false}, Location: c.pos(n.GetStart())} + } + + if an := n.An_id_or_type(); an != nil { + idText := parseAnIdOrType(an) + return &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: "@"}}}, + Rexpr: &ast.String{Str: idText}, + Location: c.pos(n.GetStart()), + } + } + c.paramCount++ + return &ast.ParamRef{ + Number: c.paramCount, + Location: c.pos(n.GetStart()), + Dollar: true, + } + } + return &ast.TODO{} +} + +func (c *cc) convertWildCardField(n *parser.Result_columnContext) *ast.ColumnRef { + prefixCtx := n.Opt_id_prefix() + prefix := c.convertOptIdPrefix(prefixCtx) + + items := []ast.Node{} + if prefix != "" { + items = append(items, NewIdentifier(prefix)) + } + + items = append(items, &ast.A_Star{}) + return &ast.ColumnRef{ + Fields: &ast.List{Items: items}, + Location: c.pos(n.GetStart()), + } +} + +func (c *cc) convertOptIdPrefix(ctx parser.IOpt_id_prefixContext) string { + if ctx == nil { + return "" + } + if ctx.An_id() != nil { + return ctx.An_id().GetText() + } + return "" +} + +func (c *cc) convertCreate_table_stmtContext(n *parser.Create_table_stmtContext) ast.Node { + stmt := &ast.CreateTableStmt{ + Name: parseTableName(n.Simple_table_ref().Simple_table_ref_core()), + IfNotExists: n.EXISTS() != nil, + } + for _, idef := range n.AllCreate_table_entry() { + if def, ok := idef.(*parser.Create_table_entryContext); ok { + switch { + case def.Column_schema() != nil: + if colCtx, ok := def.Column_schema().(*parser.Column_schemaContext); ok { + colDef := c.convertColumnSchema(colCtx) + if colDef != nil { + stmt.Cols = append(stmt.Cols, colDef) + } + } + case def.Table_constraint() != nil: + if conCtx, ok := def.Table_constraint().(*parser.Table_constraintContext); ok { + switch { + case conCtx.PRIMARY() != nil && conCtx.KEY() != nil: + for _, cname := range conCtx.AllAn_id() { + for _, col := range stmt.Cols { + if col.Colname == parseAnId(cname) { + col.IsNotNull = true + } + } + } + case conCtx.PARTITION() != nil && conCtx.BY() != nil: + _ = conCtx + // todo: partition by constraint + case conCtx.ORDER() != nil && conCtx.BY() != nil: + _ = conCtx + // todo: order by constraint + } + } + + case def.Table_index() != nil: + if indCtx, ok := def.Table_index().(*parser.Table_indexContext); ok { + _ = indCtx + // todo + } + case def.Family_entry() != nil: + if famCtx, ok := def.Family_entry().(*parser.Family_entryContext); ok { + _ = famCtx + // todo + } + case def.Changefeed() != nil: // таблица ориентированная + if cgfCtx, ok := def.Changefeed().(*parser.ChangefeedContext); ok { + _ = cgfCtx + // todo + } + } + } + } + return stmt +} + +func (c *cc) convertColumnSchema(n *parser.Column_schemaContext) *ast.ColumnDef { + + col := &ast.ColumnDef{} + + if anId := n.An_id_schema(); anId != nil { + col.Colname = identifier(parseAnIdSchema(anId)) + } + if tnb := n.Type_name_or_bind(); tnb != nil { + col.TypeName = c.convertTypeNameOrBind(tnb) + } + if colCons := n.Opt_column_constraints(); colCons != nil { + col.IsNotNull = colCons.NOT() != nil && colCons.NULL() != nil + //todo: cover exprs if needed + } + // todo: family + + return col +} + +func (c *cc) convertTypeNameOrBind(n parser.IType_name_or_bindContext) *ast.TypeName { + if t := n.Type_name(); t != nil { + return c.convertTypeName(t) + } else if b := n.Bind_parameter(); b != nil { + return &ast.TypeName{Name: "BIND:" + identifier(parseAnIdOrType(b.An_id_or_type()))} + } + return nil +} + +func (c *cc) convertTypeName(n parser.IType_nameContext) *ast.TypeName { + if n == nil { + return nil + } + + if composite := n.Type_name_composite(); composite != nil { + if node := c.convertTypeNameComposite(composite); node != nil { + if typeName, ok := node.(*ast.TypeName); ok { + return typeName + } + } + } + + if decimal := n.Type_name_decimal(); decimal != nil { + if integerOrBinds := decimal.AllInteger_or_bind(); len(integerOrBinds) >= 2 { + return &ast.TypeName{ + Name: "Decimal", + TypeOid: 0, + Names: &ast.List{ + Items: []ast.Node{ + c.convertIntegerOrBind(integerOrBinds[0]), + c.convertIntegerOrBind(integerOrBinds[1]), + }, + }, + } + } + } + + // Handle simple types + if simple := n.Type_name_simple(); simple != nil { + return &ast.TypeName{ + Name: simple.GetText(), + TypeOid: 0, + } + } + + return nil +} + +func (c *cc) convertIntegerOrBind(n parser.IInteger_or_bindContext) ast.Node { + if n == nil { + return nil + } + + if integer := n.Integer(); integer != nil { + val, err := parseIntegerValue(integer.GetText()) + if err != nil { + return &ast.TODO{} + } + return &ast.Integer{Ival: val} + } + + if bind := n.Bind_parameter(); bind != nil { + return c.convertBindParameter(bind.(*parser.Bind_parameterContext)) + } + + return nil +} + +func (c *cc) convertTypeNameComposite(n parser.IType_name_compositeContext) ast.Node { + if n == nil { + return nil + } + + if opt := n.Type_name_optional(); opt != nil { + if typeName := opt.Type_name_or_bind(); typeName != nil { + return &ast.TypeName{ + Name: "Optional", + TypeOid: 0, + Names: &ast.List{ + Items: []ast.Node{c.convertTypeNameOrBind(typeName)}, + }, + } + } + } + + if tuple := n.Type_name_tuple(); tuple != nil { + if typeNames := tuple.AllType_name_or_bind(); len(typeNames) > 0 { + var items []ast.Node + for _, tn := range typeNames { + items = append(items, c.convertTypeNameOrBind(tn)) + } + return &ast.TypeName{ + Name: "Tuple", + TypeOid: 0, + Names: &ast.List{Items: items}, + } + } + } + + if struct_ := n.Type_name_struct(); struct_ != nil { + if structArgs := struct_.AllStruct_arg(); len(structArgs) > 0 { + var items []ast.Node + for range structArgs { + // TODO: Handle struct field names and types + items = append(items, &ast.TODO{}) + } + return &ast.TypeName{ + Name: "Struct", + TypeOid: 0, + Names: &ast.List{Items: items}, + } + } + } + + if variant := n.Type_name_variant(); variant != nil { + if variantArgs := variant.AllVariant_arg(); len(variantArgs) > 0 { + var items []ast.Node + for range variantArgs { + // TODO: Handle variant arguments + items = append(items, &ast.TODO{}) + } + return &ast.TypeName{ + Name: "Variant", + TypeOid: 0, + Names: &ast.List{Items: items}, + } + } + } + + if list := n.Type_name_list(); list != nil { + if typeName := list.Type_name_or_bind(); typeName != nil { + return &ast.TypeName{ + Name: "List", + TypeOid: 0, + Names: &ast.List{ + Items: []ast.Node{c.convertTypeNameOrBind(typeName)}, + }, + } + } + } + + if stream := n.Type_name_stream(); stream != nil { + if typeName := stream.Type_name_or_bind(); typeName != nil { + return &ast.TypeName{ + Name: "Stream", + TypeOid: 0, + Names: &ast.List{ + Items: []ast.Node{c.convertTypeNameOrBind(typeName)}, + }, + } + } + } + + if flow := n.Type_name_flow(); flow != nil { + if typeName := flow.Type_name_or_bind(); typeName != nil { + return &ast.TypeName{ + Name: "Flow", + TypeOid: 0, + Names: &ast.List{ + Items: []ast.Node{c.convertTypeNameOrBind(typeName)}, + }, + } + } + } + + if dict := n.Type_name_dict(); dict != nil { + if typeNames := dict.AllType_name_or_bind(); len(typeNames) >= 2 { + return &ast.TypeName{ + Name: "Dict", + TypeOid: 0, + Names: &ast.List{ + Items: []ast.Node{ + c.convertTypeNameOrBind(typeNames[0]), + c.convertTypeNameOrBind(typeNames[1]), + }, + }, + } + } + } + + if set := n.Type_name_set(); set != nil { + if typeName := set.Type_name_or_bind(); typeName != nil { + return &ast.TypeName{ + Name: "Set", + TypeOid: 0, + Names: &ast.List{ + Items: []ast.Node{c.convertTypeNameOrBind(typeName)}, + }, + } + } + } + + if enum := n.Type_name_enum(); enum != nil { + if typeTags := enum.AllType_name_tag(); len(typeTags) > 0 { + var items []ast.Node + for range typeTags { // todo: Handle enum tags + items = append(items, &ast.TODO{}) + } + return &ast.TypeName{ + Name: "Enum", + TypeOid: 0, + Names: &ast.List{Items: items}, + } + } + } + + if resource := n.Type_name_resource(); resource != nil { + if typeTag := resource.Type_name_tag(); typeTag != nil { + // TODO: Handle resource tag + return &ast.TypeName{ + Name: "Resource", + TypeOid: 0, + Names: &ast.List{ + Items: []ast.Node{&ast.TODO{}}, + }, + } + } + } + + if tagged := n.Type_name_tagged(); tagged != nil { + if typeName := tagged.Type_name_or_bind(); typeName != nil { + if typeTag := tagged.Type_name_tag(); typeTag != nil { + // TODO: Handle tagged type and tag + return &ast.TypeName{ + Name: "Tagged", + TypeOid: 0, + Names: &ast.List{ + Items: []ast.Node{ + c.convertTypeNameOrBind(typeName), + &ast.TODO{}, + }, + }, + } + } + } + } + + if callable := n.Type_name_callable(); callable != nil { + // TODO: Handle callable argument list and return type + return &ast.TypeName{ + Name: "Callable", + TypeOid: 0, + Names: &ast.List{ + Items: []ast.Node{&ast.TODO{}}, + }, + } + } + + return nil +} + +func (c *cc) convertSqlStmtCore(n parser.ISql_stmt_coreContext) ast.Node { + if n == nil { + return nil + } + + if stmt := n.Pragma_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Select_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Named_nodes_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Create_table_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Drop_table_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Use_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Into_table_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Commit_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Update_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Delete_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Rollback_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Declare_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Import_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Export_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_table_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_external_table_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Do_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Define_action_or_subquery_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.If_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.For_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Values_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Create_user_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_user_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Create_group_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_group_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Drop_role_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Create_object_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_object_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Drop_object_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Create_external_data_source_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_external_data_source_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Drop_external_data_source_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Create_replication_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Drop_replication_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Create_topic_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_topic_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Drop_topic_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Grant_permissions_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Revoke_permissions_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_table_store_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Upsert_object_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Create_view_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Drop_view_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_replication_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Create_resource_pool_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_resource_pool_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Drop_resource_pool_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Create_backup_collection_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_backup_collection_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Drop_backup_collection_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Analyze_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Create_resource_pool_classifier_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_resource_pool_classifier_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Drop_resource_pool_classifier_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Backup_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Restore_stmt(); stmt != nil { + return c.convert(stmt) + } + if stmt := n.Alter_sequence_stmt(); stmt != nil { + return c.convert(stmt) + } + return nil +} + +func (c *cc) convertExpr(n *parser.ExprContext) ast.Node { + if n == nil { + return nil + } + + if tn := n.Type_name_composite(); tn != nil { + return c.convertTypeNameComposite(tn) + } + + orSubs := n.AllOr_subexpr() + if len(orSubs) == 0 { + return nil + } + + orSub, ok := orSubs[0].(*parser.Or_subexprContext) + if !ok { + return nil + } + + left := c.convertOrSubExpr(orSub) + for i := 1; i < len(orSubs); i++ { + orSub, ok = orSubs[i].(*parser.Or_subexprContext) + if !ok { + return nil + } + right := c.convertOrSubExpr(orSub) + left = &ast.BoolExpr{ + Boolop: ast.BoolExprTypeOr, + Args: &ast.List{Items: []ast.Node{left, right}}, + Location: c.pos(n.GetStart()), + } + } + return left +} + +func (c *cc) convertOrSubExpr(n *parser.Or_subexprContext) ast.Node { + if n == nil { + return nil + } + andSubs := n.AllAnd_subexpr() + if len(andSubs) == 0 { + return nil + } + andSub, ok := andSubs[0].(*parser.And_subexprContext) + if !ok { + return nil + } + + left := c.convertAndSubexpr(andSub) + for i := 1; i < len(andSubs); i++ { + andSub, ok = andSubs[i].(*parser.And_subexprContext) + if !ok { + return nil + } + right := c.convertAndSubexpr(andSub) + left = &ast.BoolExpr{ + Boolop: ast.BoolExprTypeAnd, + Args: &ast.List{Items: []ast.Node{left, right}}, + Location: c.pos(n.GetStart()), + } + } + return left +} + +func (c *cc) convertAndSubexpr(n *parser.And_subexprContext) ast.Node { + if n == nil { + return nil + } + + xors := n.AllXor_subexpr() + if len(xors) == 0 { + return nil + } + + xor, ok := xors[0].(*parser.Xor_subexprContext) + if !ok { + return nil + } + + left := c.convertXorSubexpr(xor) + for i := 1; i < len(xors); i++ { + xor, ok = xors[i].(*parser.Xor_subexprContext) + if !ok { + return nil + } + right := c.convertXorSubexpr(xor) + left = &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: "XOR"}}}, + Lexpr: left, + Rexpr: right, + Location: c.pos(n.GetStart()), + } + } + return left +} + +func (c *cc) convertXorSubexpr(n *parser.Xor_subexprContext) ast.Node { + if n == nil { + return nil + } + es := n.Eq_subexpr() + if es == nil { + return nil + } + subExpr, ok := es.(*parser.Eq_subexprContext) + if !ok { + return nil + } + base := c.convertEqSubexpr(subExpr) + if cond := n.Cond_expr(); cond != nil { + condCtx, ok := cond.(*parser.Cond_exprContext) + if !ok { + return base + } + + switch { + case condCtx.IN() != nil: + if inExpr := condCtx.In_expr(); inExpr != nil { + return &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: "IN"}}}, + Lexpr: base, + Rexpr: c.convert(inExpr), + } + } + case condCtx.BETWEEN() != nil: + if eqSubs := condCtx.AllEq_subexpr(); len(eqSubs) >= 2 { + return &ast.BetweenExpr{ + Expr: base, + Left: c.convert(eqSubs[0]), + Right: c.convert(eqSubs[1]), + Not: condCtx.NOT() != nil, + Location: c.pos(n.GetStart()), + } + } + case condCtx.ISNULL() != nil: + return &ast.NullTest{ + Arg: base, + Nulltesttype: 1, // IS NULL + Location: c.pos(n.GetStart()), + } + case condCtx.NOTNULL() != nil: + return &ast.NullTest{ + Arg: base, + Nulltesttype: 2, // IS NOT NULL + Location: c.pos(n.GetStart()), + } + case condCtx.IS() != nil && condCtx.NULL() != nil: + return &ast.NullTest{ + Arg: base, + Nulltesttype: 1, // IS NULL + Location: c.pos(n.GetStart()), + } + case condCtx.IS() != nil && condCtx.NOT() != nil && condCtx.NULL() != nil: + return &ast.NullTest{ + Arg: base, + Nulltesttype: 2, // IS NOT NULL + Location: c.pos(n.GetStart()), + } + case condCtx.Match_op() != nil: + // debug!!! + matchOp := condCtx.Match_op().GetText() + if eqSubs := condCtx.AllEq_subexpr(); len(eqSubs) >= 1 { + expr := &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: matchOp}}}, + Lexpr: base, + Rexpr: c.convert(eqSubs[0]), + } + if condCtx.ESCAPE() != nil && len(eqSubs) >= 2 { //nolint + // todo: Add ESCAPE support + } + return expr + } + case len(condCtx.AllEQUALS()) > 0 || len(condCtx.AllEQUALS2()) > 0 || + len(condCtx.AllNOT_EQUALS()) > 0 || len(condCtx.AllNOT_EQUALS2()) > 0: + // debug!!! + var op string + switch { + case len(condCtx.AllEQUALS()) > 0: + op = "=" + case len(condCtx.AllEQUALS2()) > 0: + op = "==" + case len(condCtx.AllNOT_EQUALS()) > 0: + op = "!=" + case len(condCtx.AllNOT_EQUALS2()) > 0: + op = "<>" + } + if eqSubs := condCtx.AllEq_subexpr(); len(eqSubs) >= 1 { + return &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: op}}}, + Lexpr: base, + Rexpr: c.convert(eqSubs[0]), + } + } + case len(condCtx.AllDistinct_from_op()) > 0: + // debug!!! + distinctOps := condCtx.AllDistinct_from_op() + for _, distinctOp := range distinctOps { + if eqSubs := condCtx.AllEq_subexpr(); len(eqSubs) >= 1 { + not := distinctOp.NOT() != nil + op := "IS DISTINCT FROM" + if not { + op = "IS NOT DISTINCT FROM" + } + return &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: op}}}, + Lexpr: base, + Rexpr: c.convert(eqSubs[0]), + } + } + } + } + } + return base +} + +func (c *cc) convertEqSubexpr(n *parser.Eq_subexprContext) ast.Node { + if n == nil { + return nil + } + neqList := n.AllNeq_subexpr() + if len(neqList) == 0 { + return nil + } + neq, ok := neqList[0].(*parser.Neq_subexprContext) + if !ok { + return nil + } + left := c.convertNeqSubexpr(neq) + ops := c.collectComparisonOps(n) + for i := 1; i < len(neqList); i++ { + neq, ok = neqList[i].(*parser.Neq_subexprContext) + if !ok { + return nil + } + right := c.convertNeqSubexpr(neq) + opText := ops[i-1].GetText() + left = &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: opText}}}, + Lexpr: left, + Rexpr: right, + Location: c.pos(n.GetStart()), + } + } + return left +} + +func (c *cc) collectComparisonOps(n parser.IEq_subexprContext) []antlr.TerminalNode { + var ops []antlr.TerminalNode + for _, child := range n.GetChildren() { + if tn, ok := child.(antlr.TerminalNode); ok { + switch tn.GetText() { + case "<", "<=", ">", ">=": + ops = append(ops, tn) + } + } + } + return ops +} + +func (c *cc) convertNeqSubexpr(n *parser.Neq_subexprContext) ast.Node { + if n == nil { + return nil + } + bitList := n.AllBit_subexpr() + if len(bitList) == 0 { + return nil + } + + bl, ok := bitList[0].(*parser.Bit_subexprContext) + if !ok { + return nil + } + left := c.convertBitSubexpr(bl) + ops := c.collectBitwiseOps(n) + for i := 1; i < len(bitList); i++ { + bl, ok = bitList[i].(*parser.Bit_subexprContext) + if !ok { + return nil + } + right := c.convertBitSubexpr(bl) + opText := ops[i-1].GetText() + left = &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: opText}}}, + Lexpr: left, + Rexpr: right, + Location: c.pos(n.GetStart()), + } + } + + if n.Double_question() != nil { + nextCtx := n.Neq_subexpr() + if nextCtx != nil { + neq, ok2 := nextCtx.(*parser.Neq_subexprContext) + if !ok2 { + return nil + } + right := c.convertNeqSubexpr(neq) + left = &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: "??"}}}, + Lexpr: left, + Rexpr: right, + Location: c.pos(n.GetStart()), + } + } + } else { + // !! debug !! + qCount := len(n.AllQUESTION()) + if qCount > 0 { + questionOp := "?" + if qCount > 1 { + questionOp = strings.Repeat("?", qCount) + } + left = &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: questionOp}}}, + Lexpr: left, + Location: c.pos(n.GetStart()), + } + } + } + + return left +} + +func (c *cc) collectBitwiseOps(ctx parser.INeq_subexprContext) []antlr.TerminalNode { + var ops []antlr.TerminalNode + children := ctx.GetChildren() + for _, child := range children { + if tn, ok := child.(antlr.TerminalNode); ok { + txt := tn.GetText() + switch txt { + case "<<", ">>", "<<|", ">>|", "&", "|", "^": + ops = append(ops, tn) + } + } + } + return ops +} + +func (c *cc) convertBitSubexpr(n *parser.Bit_subexprContext) ast.Node { + addList := n.AllAdd_subexpr() + left := c.convertAddSubexpr(addList[0].(*parser.Add_subexprContext)) + + ops := c.collectBitOps(n) + for i := 1; i < len(addList); i++ { + right := c.convertAddSubexpr(addList[i].(*parser.Add_subexprContext)) + opText := ops[i-1].GetText() + left = &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: opText}}}, + Lexpr: left, + Rexpr: right, + Location: c.pos(n.GetStart()), + } + } + return left +} + +func (c *cc) collectBitOps(ctx parser.IBit_subexprContext) []antlr.TerminalNode { + var ops []antlr.TerminalNode + children := ctx.GetChildren() + for _, child := range children { + if tn, ok := child.(antlr.TerminalNode); ok { + txt := tn.GetText() + switch txt { + case "+", "-": + ops = append(ops, tn) + } + } + } + return ops +} + +func (c *cc) convertAddSubexpr(n *parser.Add_subexprContext) ast.Node { + mulList := n.AllMul_subexpr() + left := c.convertMulSubexpr(mulList[0].(*parser.Mul_subexprContext)) + + ops := c.collectAddOps(n) + for i := 1; i < len(mulList); i++ { + right := c.convertMulSubexpr(mulList[i].(*parser.Mul_subexprContext)) + opText := ops[i-1].GetText() + left = &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: opText}}}, + Lexpr: left, + Rexpr: right, + Location: c.pos(n.GetStart()), + } + } + return left +} + +func (c *cc) collectAddOps(ctx parser.IAdd_subexprContext) []antlr.TerminalNode { + var ops []antlr.TerminalNode + for _, child := range ctx.GetChildren() { + if tn, ok := child.(antlr.TerminalNode); ok { + switch tn.GetText() { + case "*", "/", "%": + ops = append(ops, tn) + } + } + } + return ops +} + +func (c *cc) convertMulSubexpr(n *parser.Mul_subexprContext) ast.Node { + conList := n.AllCon_subexpr() + left := c.convertConSubexpr(conList[0].(*parser.Con_subexprContext)) + + for i := 1; i < len(conList); i++ { + right := c.convertConSubexpr(conList[i].(*parser.Con_subexprContext)) + left = &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: "||"}}}, + Lexpr: left, + Rexpr: right, + Location: c.pos(n.GetStart()), + } + } + return left +} + +func (c *cc) convertConSubexpr(n *parser.Con_subexprContext) ast.Node { + if opCtx := n.Unary_op(); opCtx != nil { + op := opCtx.GetText() + operand := c.convertUnarySubexpr(n.Unary_subexpr().(*parser.Unary_subexprContext)) + return &ast.A_Expr{ + Name: &ast.List{Items: []ast.Node{&ast.String{Str: op}}}, + Rexpr: operand, + Location: c.pos(n.GetStart()), + } + } + return c.convertUnarySubexpr(n.Unary_subexpr().(*parser.Unary_subexprContext)) +} + +func (c *cc) convertUnarySubexpr(n *parser.Unary_subexprContext) ast.Node { + if casual := n.Unary_casual_subexpr(); casual != nil { + return c.convertUnaryCasualSubexpr(casual.(*parser.Unary_casual_subexprContext)) + } + if jsonExpr := n.Json_api_expr(); jsonExpr != nil { + return c.convertJsonApiExpr(jsonExpr.(*parser.Json_api_exprContext)) + } + return nil +} + +func (c *cc) convertJsonApiExpr(n *parser.Json_api_exprContext) ast.Node { + return todo("Json_api_exprContext", n) +} + +func (c *cc) convertUnaryCasualSubexpr(n *parser.Unary_casual_subexprContext) ast.Node { + var current ast.Node + switch { + case n.Id_expr() != nil: + current = c.convertIdExpr(n.Id_expr().(*parser.Id_exprContext)) + case n.Atom_expr() != nil: + current = c.convertAtomExpr(n.Atom_expr().(*parser.Atom_exprContext)) + default: + return todo("Unary_casual_subexprContext", n) + } + + if suffix := n.Unary_subexpr_suffix(); suffix != nil { + current = c.processSuffixChain(current, suffix.(*parser.Unary_subexpr_suffixContext)) + } + + return current +} + +func (c *cc) processSuffixChain(base ast.Node, suffix *parser.Unary_subexpr_suffixContext) ast.Node { + current := base + for i := 0; i < suffix.GetChildCount(); i++ { + child := suffix.GetChild(i) + switch elem := child.(type) { + case *parser.Key_exprContext: + current = c.handleKeySuffix(current, elem) + case *parser.Invoke_exprContext: + current = c.handleInvokeSuffix(current, elem, i) + case antlr.TerminalNode: + if elem.GetText() == "." { + current = c.handleDotSuffix(current, suffix, &i) + } + } + } + return current +} + +func (c *cc) handleKeySuffix(base ast.Node, keyCtx *parser.Key_exprContext) ast.Node { + keyNode := c.convertKey_exprContext(keyCtx) + ind, ok := keyNode.(*ast.A_Indirection) + if !ok { + return todo("Key_exprContext", keyCtx) + } + + if indirection, ok := base.(*ast.A_Indirection); ok { + indirection.Indirection.Items = append(indirection.Indirection.Items, ind.Indirection.Items...) + return indirection + } + + return &ast.A_Indirection{ + Arg: base, + Indirection: &ast.List{ + Items: []ast.Node{keyNode}, + }, + } +} + +func (c *cc) handleInvokeSuffix(base ast.Node, invokeCtx *parser.Invoke_exprContext, idx int) ast.Node { + funcCall, ok := c.convertInvoke_exprContext(invokeCtx).(*ast.FuncCall) + if !ok { + return todo("Invoke_exprContext", invokeCtx) + } + + if idx == 0 { + switch baseNode := base.(type) { + case *ast.ColumnRef: + if len(baseNode.Fields.Items) > 0 { + var nameParts []string + for _, item := range baseNode.Fields.Items { + if s, ok := item.(*ast.String); ok { + nameParts = append(nameParts, s.Str) + } + } + funcName := strings.Join(nameParts, ".") + + if funcName == "coalesce" { + return &ast.CoalesceExpr{ + Args: funcCall.Args, + Location: baseNode.Location, + } + } + + funcCall.Func = &ast.FuncName{Name: funcName} + funcCall.Funcname.Items = append(funcCall.Funcname.Items, &ast.String{Str: funcName}) + + return funcCall + } + default: + return todo("Invoke_exprContext", invokeCtx) + } + } + + stmt := &ast.RecursiveFuncCall{ + Func: base, + Funcname: funcCall.Funcname, + AggStar: funcCall.AggStar, + Location: funcCall.Location, + Args: funcCall.Args, + AggDistinct: funcCall.AggDistinct, + } + stmt.Funcname.Items = append(stmt.Funcname.Items, base) + return stmt +} + +func (c *cc) handleDotSuffix(base ast.Node, suffix *parser.Unary_subexpr_suffixContext, idx *int) ast.Node { + if *idx+1 >= suffix.GetChildCount() { + return base + } + + next := suffix.GetChild(*idx + 1) + *idx++ + + var field ast.Node + switch v := next.(type) { + case *parser.Bind_parameterContext: + field = c.convertBindParameter(v) + case *parser.An_id_or_typeContext: + field = &ast.String{Str: parseAnIdOrType(v)} + case antlr.TerminalNode: + if val, err := parseIntegerValue(v.GetText()); err == nil { + field = &ast.A_Const{Val: &ast.Integer{Ival: val}} + } else { + return &ast.TODO{} + } + } + + if field == nil { + return base + } + + if cr, ok := base.(*ast.ColumnRef); ok { + cr.Fields.Items = append(cr.Fields.Items, field) + return cr + } + return &ast.ColumnRef{ + Fields: &ast.List{Items: []ast.Node{base, field}}, + } +} + +func (c *cc) convertKey_exprContext(n *parser.Key_exprContext) ast.Node { + if n.LBRACE_SQUARE() == nil || n.RBRACE_SQUARE() == nil || n.Expr() == nil { + return todo("Key_exprContext", n) + } + + stmt := &ast.A_Indirection{ + Indirection: &ast.List{}, + } + + expr := c.convert(n.Expr()) + + stmt.Indirection.Items = append(stmt.Indirection.Items, &ast.A_Indices{ + Uidx: expr, + }) + + return stmt +} + +func (c *cc) convertInvoke_exprContext(n *parser.Invoke_exprContext) ast.Node { + if n.LPAREN() == nil || n.RPAREN() == nil { + return todo("Invoke_exprContext", n) + } + + distinct := false + if n.Opt_set_quantifier() != nil { + distinct = n.Opt_set_quantifier().DISTINCT() != nil + } + + stmt := &ast.FuncCall{ + AggDistinct: distinct, + Funcname: &ast.List{}, + AggOrder: &ast.List{}, + Args: &ast.List{}, + Location: c.pos(n.GetStart()), + } + + if nList := n.Named_expr_list(); nList != nil { + for _, namedExpr := range nList.AllNamed_expr() { + name := parseAnIdOrType(namedExpr.An_id_or_type()) + expr := c.convert(namedExpr.Expr()) + + var res ast.Node + if rt, ok := expr.(*ast.ResTarget); ok { + if name != "" { + rt.Name = &name + } + res = rt + } else if name != "" { + res = &ast.ResTarget{ + Name: &name, + Val: expr, + Location: c.pos(namedExpr.Expr().GetStart()), + } + } else { + res = expr + } + + stmt.Args.Items = append(stmt.Args.Items, res) + } + } else if n.ASTERISK() != nil { + stmt.AggStar = true + } + + return stmt +} + +func (c *cc) convertIdExpr(n *parser.Id_exprContext) ast.Node { + if id := n.Identifier(); id != nil { + return &ast.ColumnRef{ + Fields: &ast.List{ + Items: []ast.Node{ + NewIdentifier(id.GetText()), + }, + }, + Location: c.pos(id.GetStart()), + } + } + return &ast.TODO{} +} + +func (c *cc) convertAtomExpr(n *parser.Atom_exprContext) ast.Node { + switch { + case n.An_id_or_type() != nil && n.NAMESPACE() != nil: + return NewIdentifier(parseAnIdOrType(n.An_id_or_type()) + "::" + parseIdOrType(n.Id_or_type())) + case n.An_id_or_type() != nil: + return NewIdentifier(parseAnIdOrType(n.An_id_or_type())) + case n.Literal_value() != nil: + return c.convertLiteralValue(n.Literal_value().(*parser.Literal_valueContext)) + case n.Bind_parameter() != nil: + return c.convertBindParameter(n.Bind_parameter().(*parser.Bind_parameterContext)) + default: + return &ast.TODO{} + } +} + +func (c *cc) convertLiteralValue(n *parser.Literal_valueContext) ast.Node { + switch { + case n.Integer() != nil: + text := n.Integer().GetText() + val, err := parseIntegerValue(text) + if err != nil { + if debug.Active { + log.Printf("Failed to parse integer value '%s': %v", text, err) + } + return &ast.TODO{} + } + return &ast.A_Const{Val: &ast.Integer{Ival: val}, Location: c.pos(n.GetStart())} + + case n.Real_() != nil: + text := n.Real_().GetText() + return &ast.A_Const{Val: &ast.Float{Str: text}, Location: c.pos(n.GetStart())} + + case n.STRING_VALUE() != nil: // !!! debug !!! (problem with quoted strings) + val := n.STRING_VALUE().GetText() + if len(val) >= 2 { + val = val[1 : len(val)-1] + } + return &ast.A_Const{Val: &ast.String{Str: val}, Location: c.pos(n.GetStart())} + + case n.Bool_value() != nil: + var i bool + if n.Bool_value().TRUE() != nil { + i = true + } + return &ast.A_Const{Val: &ast.Boolean{Boolval: i}, Location: c.pos(n.GetStart())} + + case n.NULL() != nil: + return &ast.Null{} + + case n.CURRENT_TIME() != nil: + if debug.Active { + log.Printf("TODO: Implement CURRENT_TIME") + } + return &ast.TODO{} + + case n.CURRENT_DATE() != nil: + if debug.Active { + log.Printf("TODO: Implement CURRENT_DATE") + } + return &ast.TODO{} + + case n.CURRENT_TIMESTAMP() != nil: + if debug.Active { + log.Printf("TODO: Implement CURRENT_TIMESTAMP") + } + return &ast.TODO{} + + case n.BLOB() != nil: + blobText := n.BLOB().GetText() + return &ast.A_Const{Val: &ast.String{Str: blobText}, Location: c.pos(n.GetStart())} + + case n.EMPTY_ACTION() != nil: + if debug.Active { + log.Printf("TODO: Implement EMPTY_ACTION") + } + return &ast.TODO{} + + default: + if debug.Active { + log.Printf("Unknown literal value type: %T", n) + } + return &ast.TODO{} + } +} + +func (c *cc) convertSqlStmt(n *parser.Sql_stmtContext) ast.Node { + if n == nil { + return nil + } + // todo: handle explain + if core := n.Sql_stmt_core(); core != nil { + return c.convert(core) + } + + return nil +} + +func (c *cc) convert(node node) ast.Node { + switch n := node.(type) { + case *parser.Sql_stmtContext: + return c.convertSqlStmt(n) + + case *parser.Sql_stmt_coreContext: + return c.convertSqlStmtCore(n) + + case *parser.Create_table_stmtContext: + return c.convertCreate_table_stmtContext(n) + + case *parser.Select_stmtContext: + return c.convertSelectStmtContext(n) + + case *parser.Select_coreContext: + return c.convertSelectCoreContext(n) + + case *parser.Result_columnContext: + return c.convertResultColumn(n) + + case *parser.Join_sourceContext: + return c.convertJoinSource(n) + + case *parser.Flatten_sourceContext: + return c.convertFlattenSource(n) + + case *parser.Named_single_sourceContext: + return c.convertNamedSingleSource(n) + + case *parser.Single_sourceContext: + return c.convertSingleSource(n) + + case *parser.Bind_parameterContext: + return c.convertBindParameter(n) + + case *parser.ExprContext: + return c.convertExpr(n) + + case *parser.Or_subexprContext: + return c.convertOrSubExpr(n) + + case *parser.And_subexprContext: + return c.convertAndSubexpr(n) + + case *parser.Xor_subexprContext: + return c.convertXorSubexpr(n) + + case *parser.Eq_subexprContext: + return c.convertEqSubexpr(n) + + case *parser.Neq_subexprContext: + return c.convertNeqSubexpr(n) + + case *parser.Bit_subexprContext: + return c.convertBitSubexpr(n) + + case *parser.Add_subexprContext: + return c.convertAddSubexpr(n) + + case *parser.Mul_subexprContext: + return c.convertMulSubexpr(n) + + case *parser.Con_subexprContext: + return c.convertConSubexpr(n) + + case *parser.Unary_subexprContext: + return c.convertUnarySubexpr(n) + + case *parser.Unary_casual_subexprContext: + return c.convertUnaryCasualSubexpr(n) + + case *parser.Id_exprContext: + return c.convertIdExpr(n) + + case *parser.Atom_exprContext: + return c.convertAtomExpr(n) + + case *parser.Literal_valueContext: + return c.convertLiteralValue(n) + + case *parser.Json_api_exprContext: + return c.convertJsonApiExpr(n) + + case *parser.Type_name_compositeContext: + return c.convertTypeNameComposite(n) + + case *parser.Type_nameContext: + return c.convertTypeName(n) + + case *parser.Integer_or_bindContext: + return c.convertIntegerOrBind(n) + + case *parser.Type_name_or_bindContext: + return c.convertTypeNameOrBind(n) + + case *parser.Into_table_stmtContext: + return c.convertInto_table_stmtContext(n) + + case *parser.Values_stmtContext: + return c.convertValues_stmtContext(n) + + case *parser.Returning_columns_listContext: + return c.convertReturning_columns_listContext(n) + + case *parser.Delete_stmtContext: + return c.convertDelete_stmtContext(n) + + case *parser.Update_stmtContext: + return c.convertUpdate_stmtContext(n) + + case *parser.Drop_table_stmtContext: + return c.convertDrop_table_stmtContext(n) + + case *parser.Commit_stmtContext: + return c.convertCommit_stmtContext(n) + + case *parser.Rollback_stmtContext: + return c.convertRollback_stmtContext(n) + + case *parser.Pragma_valueContext: + return c.convertPragma_valueContext(n) + + case *parser.Pragma_stmtContext: + return c.convertPragma_stmtContext(n) + + case *parser.Use_stmtContext: + return c.convertUse_stmtContext(n) + + case *parser.Cluster_exprContext: + return c.convertCluster_exprContext(n) + + case *parser.Create_user_stmtContext: + return c.convertCreate_user_stmtContext(n) + + case *parser.Role_nameContext: + return c.convertRole_nameContext(n) + + case *parser.User_optionContext: + return c.convertUser_optionContext(n) + + case *parser.Create_group_stmtContext: + return c.convertCreate_group_stmtContext(n) + + case *parser.Alter_user_stmtContext: + return c.convertAlter_user_stmtContext(n) + + case *parser.Alter_group_stmtContext: + return c.convertAlter_group_stmtContext(n) + + case *parser.Drop_role_stmtContext: + return c.convertDrop_role_stmtCOntext(n) + + default: + return todo("convert(case=default)", n) + } +} diff --git a/internal/engine/ydb/lib/aggregate.go b/internal/engine/ydb/lib/aggregate.go new file mode 100644 index 0000000000..dfb3924e90 --- /dev/null +++ b/internal/engine/ydb/lib/aggregate.go @@ -0,0 +1,330 @@ +package lib + +import ( + "github.com/sqlc-dev/sqlc/internal/sql/ast" + "github.com/sqlc-dev/sqlc/internal/sql/catalog" +) + +func AggregateFunctions() []*catalog.Function { + var funcs []*catalog.Function + + // COUNT(*) + funcs = append(funcs, &catalog.Function{ + Name: "COUNT", + Args: []*catalog.Argument{}, + ReturnType: &ast.TypeName{Name: "Uint64"}, + }) + + // COUNT(T) и COUNT(T?) + for _, typ := range types { + funcs = append(funcs, &catalog.Function{ + Name: "COUNT", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: "Uint64"}, + }) + funcs = append(funcs, &catalog.Function{ + Name: "COUNT", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}, Mode: ast.FuncParamVariadic}, + }, + ReturnType: &ast.TypeName{Name: "Uint64"}, + }) + } + + // MIN и MAX + for _, typ := range types { + funcs = append(funcs, &catalog.Function{ + Name: "MIN", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: true, + }) + funcs = append(funcs, &catalog.Function{ + Name: "MAX", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: true, + }) + } + + // SUM для unsigned типов + for _, typ := range unsignedTypes { + funcs = append(funcs, &catalog.Function{ + Name: "SUM", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: "Uint64"}, + ReturnTypeNullable: true, + }) + } + + // SUM для signed типов + for _, typ := range signedTypes { + funcs = append(funcs, &catalog.Function{ + Name: "SUM", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: "Int64"}, + ReturnTypeNullable: true, + }) + } + + // SUM для float/double + for _, typ := range []string{"float", "double"} { + funcs = append(funcs, &catalog.Function{ + Name: "SUM", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: true, + }) + } + + // AVG для целочисленных типов + for _, typ := range append(unsignedTypes, signedTypes...) { + funcs = append(funcs, &catalog.Function{ + Name: "AVG", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: "Double"}, + ReturnTypeNullable: true, + }) + } + + // AVG для float/double + for _, typ := range []string{"float", "double"} { + funcs = append(funcs, &catalog.Function{ + Name: "AVG", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: true, + }) + } + + // COUNT_IF + funcs = append(funcs, &catalog.Function{ + Name: "COUNT_IF", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: "Bool"}}, + }, + ReturnType: &ast.TypeName{Name: "Uint64"}, + ReturnTypeNullable: true, + }) + + // SUM_IF для unsigned + for _, typ := range unsignedTypes { + funcs = append(funcs, &catalog.Function{ + Name: "SUM_IF", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: "Bool"}}, + }, + ReturnType: &ast.TypeName{Name: "Uint64"}, + ReturnTypeNullable: true, + }) + } + + // SUM_IF для signed + for _, typ := range signedTypes { + funcs = append(funcs, &catalog.Function{ + Name: "SUM_IF", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: "Bool"}}, + }, + ReturnType: &ast.TypeName{Name: "Int64"}, + ReturnTypeNullable: true, + }) + } + + // SUM_IF для float/double + for _, typ := range []string{"float", "double"} { + funcs = append(funcs, &catalog.Function{ + Name: "SUM_IF", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: "Bool"}}, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: true, + }) + } + + // AVG_IF для целочисленных + for _, typ := range append(unsignedTypes, signedTypes...) { + funcs = append(funcs, &catalog.Function{ + Name: "AVG_IF", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: "Bool"}}, + }, + ReturnType: &ast.TypeName{Name: "Double"}, + ReturnTypeNullable: true, + }) + } + + // AVG_IF для float/double + for _, typ := range []string{"float", "double"} { + funcs = append(funcs, &catalog.Function{ + Name: "AVG_IF", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: "Bool"}}, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: true, + }) + } + + // SOME + for _, typ := range types { + funcs = append(funcs, &catalog.Function{ + Name: "SOME", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: true, + }) + } + + // AGGREGATE_LIST и AGGREGATE_LIST_DISTINCT + for _, typ := range types { + funcs = append(funcs, &catalog.Function{ + Name: "AGGREGATE_LIST", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: "List<" + typ + ">"}, + }) + funcs = append(funcs, &catalog.Function{ + Name: "AGGREGATE_LIST_DISTINCT", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: "List<" + typ + ">"}, + }) + } + + // BOOL_AND, BOOL_OR, BOOL_XOR + boolAggrs := []string{"BOOL_AND", "BOOL_OR", "BOOL_XOR"} + for _, name := range boolAggrs { + funcs = append(funcs, &catalog.Function{ + Name: name, + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: "Bool"}}, + }, + ReturnType: &ast.TypeName{Name: "Bool"}, + ReturnTypeNullable: true, + }) + } + + // BIT_AND, BIT_OR, BIT_XOR + bitAggrs := []string{"BIT_AND", "BIT_OR", "BIT_XOR"} + for _, typ := range append(unsignedTypes, signedTypes...) { + for _, name := range bitAggrs { + funcs = append(funcs, &catalog.Function{ + Name: name, + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: true, + }) + } + } + + // STDDEV и VARIANCE + stdDevVariants := []struct { + name string + returnType string + }{ + {"STDDEV", "Double"}, + {"VARIANCE", "Double"}, + {"STDDEV_SAMPLE", "Double"}, + {"VARIANCE_SAMPLE", "Double"}, + {"STDDEV_POPULATION", "Double"}, + {"VARIANCE_POPULATION", "Double"}, + } + for _, variant := range stdDevVariants { + funcs = append(funcs, &catalog.Function{ + Name: variant.name, + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: "Double"}}, + }, + ReturnType: &ast.TypeName{Name: variant.returnType}, + ReturnTypeNullable: true, + }) + } + + // CORRELATION и COVARIANCE + corrCovar := []string{"CORRELATION", "COVARIANCE", "COVARIANCE_SAMPLE", "COVARIANCE_POPULATION"} + for _, name := range corrCovar { + funcs = append(funcs, &catalog.Function{ + Name: name, + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: "Double"}}, + {Type: &ast.TypeName{Name: "Double"}}, + }, + ReturnType: &ast.TypeName{Name: "Double"}, + ReturnTypeNullable: true, + }) + } + + // HISTOGRAM + funcs = append(funcs, &catalog.Function{ + Name: "HISTOGRAM", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: "Double"}}, + }, + ReturnType: &ast.TypeName{Name: "HistogramStruct"}, + ReturnTypeNullable: true, + }) + + // TOP и BOTTOM + topBottom := []string{"TOP", "BOTTOM"} + for _, name := range topBottom { + for _, typ := range types { + funcs = append(funcs, &catalog.Function{ + Name: name, + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: "Uint32"}}, + }, + ReturnType: &ast.TypeName{Name: "List<" + typ + ">"}, + }) + } + } + + // MAX_BY и MIN_BY + minMaxBy := []string{"MAX_BY", "MIN_BY"} + for _, name := range minMaxBy { + for _, typ := range types { + funcs = append(funcs, &catalog.Function{ + Name: name, + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: "any"}}, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: true, + }) + } + } + + // ... (добавьте другие агрегатные функции по аналогии) + + return funcs +} diff --git a/internal/engine/ydb/lib/basic.go b/internal/engine/ydb/lib/basic.go new file mode 100644 index 0000000000..08c0011787 --- /dev/null +++ b/internal/engine/ydb/lib/basic.go @@ -0,0 +1,203 @@ +package lib + +import ( + "github.com/sqlc-dev/sqlc/internal/sql/ast" + "github.com/sqlc-dev/sqlc/internal/sql/catalog" +) + +var types = []string{ + "bool", + "int8", "int16", "int32", "int64", + "uint8", "uint16", "uint32", "uint64", + "float", "double", + "string", "utf8", + "any", +} + +var ( + unsignedTypes = []string{"uint8", "uint16", "uint32", "uint64"} + signedTypes = []string{"int8", "int16", "int32", "int64"} + numericTypes = append(append(unsignedTypes, signedTypes...), "float", "double") +) + +func BasicFunctions() []*catalog.Function { + var funcs []*catalog.Function + + for _, typ := range types { + // COALESCE, NVL + funcs = append(funcs, &catalog.Function{ + Name: "COALESCE", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: typ}}, + { + Type: &ast.TypeName{Name: typ}, + Mode: ast.FuncParamVariadic, + }, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: false, + }) + funcs = append(funcs, &catalog.Function{ + Name: "NVL", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: typ}}, + { + Type: &ast.TypeName{Name: typ}, + Mode: ast.FuncParamVariadic, + }, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: false, + }) + + // IF(Bool, T, T) -> T + funcs = append(funcs, &catalog.Function{ + Name: "IF", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: "Bool"}}, + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: typ}, + ReturnTypeNullable: false, + }) + + // LENGTH, LEN + funcs = append(funcs, &catalog.Function{ + Name: "LENGTH", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: "Uint32"}, + ReturnTypeNullable: true, + }) + funcs = append(funcs, &catalog.Function{ + Name: "LEN", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: "Uint32"}, + ReturnTypeNullable: true, + }) + + // StartsWith, EndsWith + funcs = append(funcs, &catalog.Function{ + Name: "StartsWith", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: "Bool"}, + }) + funcs = append(funcs, &catalog.Function{ + Name: "EndsWith", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: "Bool"}, + }) + + // ABS(T) -> T + } + + // SUBSTRING + funcs = append(funcs, &catalog.Function{ + Name: "Substring", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: "String"}}, + }, + ReturnType: &ast.TypeName{Name: "String"}, + }) + funcs = append(funcs, &catalog.Function{ + Name: "Substring", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: "String"}}, + {Type: &ast.TypeName{Name: "Uint32"}}, + }, + ReturnType: &ast.TypeName{Name: "String"}, + }) + funcs = append(funcs, &catalog.Function{ + Name: "Substring", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: "String"}}, + {Type: &ast.TypeName{Name: "Uint32"}}, + {Type: &ast.TypeName{Name: "Uint32"}}, + }, + ReturnType: &ast.TypeName{Name: "String"}, + }) + + // FIND / RFIND + for _, name := range []string{"FIND", "RFIND"} { + for _, typ := range []string{"String", "Utf8"} { + funcs = append(funcs, &catalog.Function{ + Name: name, + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: "Uint32"}, + }) + funcs = append(funcs, &catalog.Function{ + Name: name, + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: typ}}, + {Type: &ast.TypeName{Name: "Uint32"}}, + }, + ReturnType: &ast.TypeName{Name: "Uint32"}, + }) + } + } + + for _, typ := range numericTypes { + funcs = append(funcs, &catalog.Function{ + Name: "Abs", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: typ}}, + }, + ReturnType: &ast.TypeName{Name: typ}, + }) + } + + // NANVL + funcs = append(funcs, &catalog.Function{ + Name: "NANVL", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: "Float"}}, + {Type: &ast.TypeName{Name: "Float"}}, + }, + ReturnType: &ast.TypeName{Name: "Float"}, + }) + funcs = append(funcs, &catalog.Function{ + Name: "NANVL", + Args: []*catalog.Argument{ + {Type: &ast.TypeName{Name: "Double"}}, + {Type: &ast.TypeName{Name: "Double"}}, + }, + ReturnType: &ast.TypeName{Name: "Double"}, + }) + + // Random* + funcs = append(funcs, &catalog.Function{ + Name: "Random", + Args: []*catalog.Argument{}, + ReturnType: &ast.TypeName{Name: "Double"}, + }) + funcs = append(funcs, &catalog.Function{ + Name: "RandomNumber", + Args: []*catalog.Argument{}, + ReturnType: &ast.TypeName{Name: "Uint64"}, + }) + funcs = append(funcs, &catalog.Function{ + Name: "RandomUuid", + Args: []*catalog.Argument{}, + ReturnType: &ast.TypeName{Name: "Uuid"}, + }) + + // todo: add all remain functions + + return funcs +} diff --git a/internal/engine/ydb/parse.go b/internal/engine/ydb/parse.go new file mode 100755 index 0000000000..1c263924a5 --- /dev/null +++ b/internal/engine/ydb/parse.go @@ -0,0 +1,94 @@ +package ydb + +import ( + "errors" + "fmt" + "io" + + "github.com/antlr4-go/antlr/v4" + "github.com/sqlc-dev/sqlc/internal/source" + "github.com/sqlc-dev/sqlc/internal/sql/ast" + parser "github.com/ydb-platform/yql-parsers/go" +) + +type errorListener struct { + *antlr.DefaultErrorListener + + err string +} + +func (el *errorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol interface{}, line, column int, msg string, e antlr.RecognitionException) { + el.err = msg +} + +// func (el *errorListener) ReportAmbiguity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex int, exact bool, ambigAlts *antlr.BitSet, configs antlr.ATNConfigSet) { +// } +// +// func (el *errorListener) ReportAttemptingFullContext(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex int, conflictingAlts *antlr.BitSet, configs antlr.ATNConfigSet) { +// } +// +// func (el *errorListener) ReportContextSensitivity(recognizer antlr.Parser, dfa *antlr.DFA, startIndex, stopIndex, prediction int, configs antlr.ATNConfigSet) { +// } + +func NewParser() *Parser { + return &Parser{} +} + +type Parser struct { +} + +func (p *Parser) Parse(r io.Reader) ([]ast.Statement, error) { + blob, err := io.ReadAll(r) + if err != nil { + return nil, err + } + content := string(blob) + input := antlr.NewInputStream(content) + lexer := parser.NewYQLLexer(input) + stream := antlr.NewCommonTokenStream(lexer, 0) + pp := parser.NewYQLParser(stream) + el := &errorListener{} + pp.AddErrorListener(el) + // pp.BuildParseTrees = true + tree := pp.Sql_query() + if el.err != "" { + return nil, errors.New(el.err) + } + pctx, ok := tree.(*parser.Sql_queryContext) + if !ok { + return nil, fmt.Errorf("expected ParserContext; got %T\n ", tree) + } + var stmts []ast.Statement + stmtListCtx := pctx.Sql_stmt_list() + if stmtListCtx != nil { + loc := 0 + for _, stmt := range stmtListCtx.AllSql_stmt() { + converter := &cc{content: string(blob)} + out := converter.convert(stmt) + if _, ok := out.(*ast.TODO); ok { + loc = byteOffset(content, stmt.GetStop().GetStop() + 2) + continue + } + if out != nil { + len := byteOffset(content, stmt.GetStop().GetStop() + 1) - loc + stmts = append(stmts, ast.Statement{ + Raw: &ast.RawStmt{ + Stmt: out, + StmtLocation: loc, + StmtLen: len, + }, + }) + loc = byteOffset(content, stmt.GetStop().GetStop() + 2) + } + } + } + return stmts, nil +} + +func (p *Parser) CommentSyntax() source.CommentSyntax { + return source.CommentSyntax{ + Dash: true, + Hash: false, + SlashStar: true, + } +} diff --git a/internal/engine/ydb/reserved.go b/internal/engine/ydb/reserved.go new file mode 100644 index 0000000000..8db504c0b9 --- /dev/null +++ b/internal/engine/ydb/reserved.go @@ -0,0 +1,301 @@ +package ydb + +import "strings" + + +func (p *Parser) IsReservedKeyword(s string) bool { + switch strings.ToLower(s) { + case "abort": + case "action": + case "add": + case "after": + case "all": + case "alter": + case "analyze": + case "and": + case "ansi": + case "any": + case "array": + case "as": + case "asc": + case "assume": + case "asymmetric": + case "async": + case "at": + case "attach": + case "attributes": + case "autoincrement": + case "automap": + case "backup": + case "batch": + case "collection": + case "before": + case "begin": + case "bernoulli": + case "between": + case "bitcast": + case "by": + case "callable": + case "cascade": + case "case": + case "cast": + case "changefeed": + case "check": + case "classifier": + case "collate": + case "column": + case "columns": + case "commit": + case "compact": + case "conditional": + case "conflict": + case "connect": + case "constraint": + case "consumer": + case "cover": + case "create": + case "cross": + case "cube": + case "current": + case "current_date": + case "current_time": + case "current_timestamp": + case "data": + case "database": + case "decimal": + case "declare": + case "default": + case "deferrable": + case "deferred": + case "define": + case "delete": + case "desc": + case "describe": + case "detach": + case "dict": + case "directory": + case "disable": + case "discard": + case "distinct": + case "do": + case "drop": + case "each": + case "else": + case "empty": + case "empty_action": + case "encrypted": + case "end": + case "enum": + case "erase": + case "error": + case "escape": + case "evaluate": + case "except": + case "exclude": + case "exclusion": + case "exclusive": + case "exists": + case "explain": + case "export": + case "external": + case "fail": + case "false": + case "family": + case "filter": + case "first": + case "flatten": + case "flow": + case "following": + case "for": + case "foreign": + case "from": + case "full": + case "function": + case "glob": + case "global": + case "grant": + case "group": + case "grouping": + case "groups": + case "hash": + case "having": + case "hop": + case "if": + case "ignore": + case "ilike": + case "immediate": + case "import": + case "in": + case "increment": + case "incremental": + case "index": + case "indexed": + case "inherits": + case "initial": + case "initially": + case "inner": + case "insert": + case "instead": + case "intersect": + case "into": + case "is": + case "isnull": + case "join": + case "json_exists": + case "json_query": + case "json_value": + case "key": + case "last": + case "left": + case "legacy": + case "like": + case "limit": + case "list": + case "local": + case "login": + case "manage": + case "match": + case "matches": + case "match_recognize": + case "measures": + case "microseconds": + case "milliseconds": + case "modify": + case "nanoseconds": + case "natural": + case "next": + case "no": + case "nologin": + case "not": + case "notnull": + case "null": + case "nulls": + case "object": + case "of": + case "offset": + case "omit": + case "on": + case "one": + case "only": + case "option": + case "optional": + case "or": + case "order": + case "others": + case "outer": + case "over": + case "owner": + case "parallel": + case "partition": + case "passing": + case "password": + case "past": + case "pattern": + case "per": + case "permute": + case "plan": + case "pool": + case "pragma": + case "preceding": + case "presort": + case "primary": + case "privileges": + case "process": + case "query": + case "queue": + case "raise": + case "range": + case "reduce": + case "references": + case "regexp": + case "reindex": + case "release": + case "remove": + case "rename": + case "repeatable": + case "replace": + case "replication": + case "reset": + case "resource": + case "respect": + case "restart": + case "restore": + case "restrict": + case "result": + case "return": + case "returning": + case "revert": + case "revoke": + case "right": + case "rlike": + case "rollback": + case "rollup": + case "row": + case "rows": + case "sample": + case "savepoint": + case "schema": + case "seconds": + case "seek": + case "select": + case "semi": + case "set": + case "sets": + case "show": + case "tskip": + case "sequence": + case "source": + case "start": + case "stream": + case "struct": + case "subquery": + case "subset": + case "symbols": + case "symmetric": + case "sync": + case "system": + case "table": + case "tables": + case "tablesample": + case "tablestore": + case "tagged": + case "temp": + case "temporary": + case "then": + case "ties": + case "to": + case "topic": + case "transaction": + case "transfer": + case "trigger": + case "true": + case "tuple": + case "type": + case "unbounded": + case "unconditional": + case "union": + case "unique": + case "unknown": + case "unmatched": + case "update": + case "upsert": + case "use": + case "user": + case "using": + case "vacuum": + case "values": + case "variant": + case "view": + case "virtual": + case "when": + case "where": + case "window": + case "with": + case "without": + case "wrapper": + case "xor": + default: + return false + } + return true +} diff --git a/internal/engine/ydb/stdlib.go b/internal/engine/ydb/stdlib.go new file mode 100644 index 0000000000..21dc21242b --- /dev/null +++ b/internal/engine/ydb/stdlib.go @@ -0,0 +1,18 @@ +package ydb + +import ( + "github.com/sqlc-dev/sqlc/internal/engine/ydb/lib" + "github.com/sqlc-dev/sqlc/internal/sql/catalog" +) + +func defaultSchema(name string) *catalog.Schema { + s := &catalog.Schema{ + Name: name, + Funcs: make([]*catalog.Function, 0, 128), + } + + s.Funcs = append(s.Funcs, lib.BasicFunctions()...) + s.Funcs = append(s.Funcs, lib.AggregateFunctions()...) + + return s +} diff --git a/internal/engine/ydb/utils.go b/internal/engine/ydb/utils.go new file mode 100755 index 0000000000..3847ee5055 --- /dev/null +++ b/internal/engine/ydb/utils.go @@ -0,0 +1,196 @@ +package ydb + +import ( + "strconv" + "strings" + "unicode/utf8" + + "github.com/antlr4-go/antlr/v4" + "github.com/sqlc-dev/sqlc/internal/sql/ast" + parser "github.com/ydb-platform/yql-parsers/go" +) + +type objectRefProvider interface { + antlr.ParserRuleContext + Object_ref() parser.IObject_refContext +} + +func parseTableName(ctx objectRefProvider) *ast.TableName { + return parseObjectRef(ctx.Object_ref()) +} + +func parseObjectRef(r parser.IObject_refContext) *ast.TableName { + if r == nil { + return nil + } + ref := r.(*parser.Object_refContext) + + parts := []string{} + + if cl := ref.Cluster_expr(); cl != nil { + parts = append(parts, parseClusterExpr(cl)) + } + + if idOrAt := ref.Id_or_at(); idOrAt != nil { + parts = append(parts, parseIdOrAt(idOrAt)) + } + + objectName := strings.Join(parts, ".") + + return &ast.TableName{ + Schema: "", + Name: identifier(objectName), + } +} + +func parseClusterExpr(ctx parser.ICluster_exprContext) string { + if ctx == nil { + return "" + } + return identifier(ctx.GetText()) +} + +func parseIdOrAt(ctx parser.IId_or_atContext) string { + if ctx == nil { + return "" + } + idOrAt := ctx.(*parser.Id_or_atContext) + + if ao := idOrAt.An_id_or_type(); ao != nil { + return identifier(parseAnIdOrType(ao)) + } + return "" +} + +func parseAnIdOrType(ctx parser.IAn_id_or_typeContext) string { + if ctx == nil { + return "" + } + anId := ctx.(*parser.An_id_or_typeContext) + + if anId.Id_or_type() != nil { + return identifier(parseIdOrType(anId.Id_or_type())) + } + + if anId.STRING_VALUE() != nil { + return identifier(anId.STRING_VALUE().GetText()) + } + + return "" +} + +func parseIdOrType(ctx parser.IId_or_typeContext) string { + if ctx == nil { + return "" + } + Id := ctx.(*parser.Id_or_typeContext) + if Id.Id() != nil { + return identifier(parseIdTable(Id.Id())) + } + + return "" +} + +func parseAnId(ctx parser.IAn_idContext) string { + if id := ctx.Id(); id != nil { + return id.GetText() + } else if str := ctx.STRING_VALUE(); str != nil { + return str.GetText() + } + return "" +} + +func parseAnIdSchema(ctx parser.IAn_id_schemaContext) string { + if ctx == nil { + return "" + } + if id := ctx.Id_schema(); id != nil { + return id.GetText() + } else if str := ctx.STRING_VALUE(); str != nil { + return str.GetText() + } + return "" +} + +func parseIdTable(ctx parser.IIdContext) string { + if ctx == nil { + return "" + } + return ctx.GetText() +} + +func parseIntegerValue(text string) (int64, error) { + text = strings.ToLower(text) + base := 10 + + switch { + case strings.HasPrefix(text, "0x"): + base = 16 + text = strings.TrimPrefix(text, "0x") + + case strings.HasPrefix(text, "0o"): + base = 8 + text = strings.TrimPrefix(text, "0o") + + case strings.HasPrefix(text, "0b"): + base = 2 + text = strings.TrimPrefix(text, "0b") + } + + // debug!!! + text = strings.TrimRight(text, "pulstibn") + + return strconv.ParseInt(text, base, 64) +} + +func (c *cc) extractRoleSpec(n parser.IRole_nameContext, roletype ast.RoleSpecType) (*ast.RoleSpec, bool, ast.Node) { + roleNode := c.convert(n) + + roleSpec := &ast.RoleSpec{ + Roletype: roletype, + Location: n.GetStart().GetStart(), + } + + isParam := true + switch v := roleNode.(type) { + case *ast.A_Const: + switch val := v.Val.(type) { + case *ast.String: + roleSpec.Rolename = &val.Str + isParam = false + case *ast.Boolean: + roleSpec.BindRolename = roleNode + default: + return nil, false, nil + } + case *ast.ParamRef, *ast.A_Expr: + roleSpec.BindRolename = roleNode + default: + return nil, false, nil + } + + return roleSpec, isParam, roleNode +} + +func byteOffset(s string, runeIndex int) int { + count := 0 + for i := range s { + if count == runeIndex { + return i + } + count++ + } + return len(s) +} + +func byteOffsetFromRuneIndex(s string, runeIndex int) int { + if runeIndex <= 0 { + return 0 + } + bytePos := 0 + for i := 0; i < runeIndex && bytePos < len(s); i++ { + _, size := utf8.DecodeRuneInString(s[bytePos:]) + bytePos += size + } + return bytePos +} diff --git a/internal/sql/ast/create_role_stmt.go b/internal/sql/ast/create_role_stmt.go index 144540863e..44565fb64c 100644 --- a/internal/sql/ast/create_role_stmt.go +++ b/internal/sql/ast/create_role_stmt.go @@ -4,6 +4,9 @@ type CreateRoleStmt struct { StmtType RoleStmtType Role *string Options *List + + // YDB specific + BindRole Node } func (n *CreateRoleStmt) Pos() int { diff --git a/internal/sql/ast/delete_stmt.go b/internal/sql/ast/delete_stmt.go index d77f043a12..715f976ae6 100644 --- a/internal/sql/ast/delete_stmt.go +++ b/internal/sql/ast/delete_stmt.go @@ -1,12 +1,18 @@ package ast type DeleteStmt struct { - Relations *List - UsingClause *List - WhereClause Node - LimitCount Node + Relations *List + UsingClause *List + WhereClause Node + LimitCount Node + ReturningList *List WithClause *WithClause + + // YDB specific + Batch bool + OnCols *List + OnSelectStmt Node } func (n *DeleteStmt) Pos() int { @@ -22,6 +28,9 @@ func (n *DeleteStmt) Format(buf *TrackedBuffer) { buf.astFormat(n.WithClause) buf.WriteString(" ") } + if n.Batch { + buf.WriteString("BATCH ") + } buf.WriteString("DELETE FROM ") if items(n.Relations) { @@ -33,6 +42,20 @@ func (n *DeleteStmt) Format(buf *TrackedBuffer) { buf.astFormat(n.WhereClause) } + if items(n.OnCols) || set(n.OnSelectStmt) { + buf.WriteString(" ON ") + + if items(n.OnCols) { + buf.WriteString("(") + buf.astFormat(n.OnCols) + buf.WriteString(") ") + } + + if set(n.OnSelectStmt) { + buf.astFormat(n.OnSelectStmt) + } + } + if set(n.LimitCount) { buf.WriteString(" LIMIT ") buf.astFormat(n.LimitCount) diff --git a/internal/sql/ast/insert_stmt.go b/internal/sql/ast/insert_stmt.go index 3cdf854091..b3e7c60809 100644 --- a/internal/sql/ast/insert_stmt.go +++ b/internal/sql/ast/insert_stmt.go @@ -23,8 +23,24 @@ func (n *InsertStmt) Format(buf *TrackedBuffer) { buf.astFormat(n.WithClause) buf.WriteString(" ") } - - buf.WriteString("INSERT INTO ") + if n.OnConflictClause != nil { + switch n.OnConflictClause.Action { + case OnConflictAction_INSERT_OR_ABORT: + buf.WriteString("INSERT OR ABORT INTO ") + case OnConflictAction_INSERT_OR_REVERT: + buf.WriteString("INSERT OR REVERT INTO ") + case OnConflictAction_INSERT_OR_IGNORE: + buf.WriteString("INSERT OR IGNORE INTO ") + case OnConflictAction_UPSERT: + buf.WriteString("UPSERT INTO ") + case OnConflictAction_REPLACE: + buf.WriteString("REPLACE INTO ") + default: + buf.WriteString("INSERT INTO ") + } + } else { + buf.WriteString("INSERT INTO ") + } if n.Relation != nil { buf.astFormat(n.Relation) } @@ -38,7 +54,7 @@ func (n *InsertStmt) Format(buf *TrackedBuffer) { buf.astFormat(n.SelectStmt) } - if n.OnConflictClause != nil { + if n.OnConflictClause != nil && n.OnConflictClause.Action < 4 { buf.WriteString(" ON CONFLICT DO NOTHING ") } diff --git a/internal/sql/ast/on_conflict_action_type.go b/internal/sql/ast/on_conflict_action_type.go new file mode 100644 index 0000000000..c149fe8d04 --- /dev/null +++ b/internal/sql/ast/on_conflict_action_type.go @@ -0,0 +1,15 @@ +package ast + +const ( + OnConflictAction_ON_CONFLICT_ACTION_UNDEFINED OnConflictAction = 0 + OnConflictAction_ONCONFLICT_NONE OnConflictAction = 1 + OnConflictAction_ONCONFLICT_NOTHING OnConflictAction = 2 + OnConflictAction_ONCONFLICT_UPDATE OnConflictAction = 3 + + // YQL-specific + OnConflictAction_INSERT_OR_ABORT OnConflictAction = 4 + OnConflictAction_INSERT_OR_REVERT OnConflictAction = 5 + OnConflictAction_INSERT_OR_IGNORE OnConflictAction = 6 + OnConflictAction_UPSERT OnConflictAction = 7 + OnConflictAction_REPLACE OnConflictAction = 8 +) diff --git a/internal/sql/ast/param_ref.go b/internal/sql/ast/param_ref.go index 8bd724993d..6ffe8cc5f0 100644 --- a/internal/sql/ast/param_ref.go +++ b/internal/sql/ast/param_ref.go @@ -6,6 +6,9 @@ type ParamRef struct { Number int Location int Dollar bool + + // YDB specific + Plike bool } func (n *ParamRef) Pos() int { @@ -16,5 +19,9 @@ func (n *ParamRef) Format(buf *TrackedBuffer) { if n == nil { return } + if n.Plike { + fmt.Fprintf(buf, "$p%d", n.Number) + return + } fmt.Fprintf(buf, "$%d", n.Number) } diff --git a/internal/sql/ast/pragma_stmt.go b/internal/sql/ast/pragma_stmt.go new file mode 100644 index 0000000000..46dc40dbf5 --- /dev/null +++ b/internal/sql/ast/pragma_stmt.go @@ -0,0 +1,43 @@ +package ast + +// YDB specific +type Pragma_stmt struct { + Name Node + Cols *List + Equals bool + Values *List + Location int +} + +func (n *Pragma_stmt) Pos() int { + return n.Location +} + +func (n *Pragma_stmt) Format(buf *TrackedBuffer) { + if n == nil { + return + } + + buf.WriteString("PRAGMA ") + if n.Name != nil { + buf.astFormat(n.Name) + } + if n.Cols != nil { + buf.astFormat(n.Cols) + } + + if n.Equals { + buf.WriteString(" = ") + } + + if n.Values != nil { + if n.Equals { + buf.astFormat(n.Values) + } else { + buf.WriteString("(") + buf.astFormat(n.Values) + buf.WriteString(")") + } + } + +} diff --git a/internal/sql/ast/recursive_func_call.go b/internal/sql/ast/recursive_func_call.go new file mode 100644 index 0000000000..1c7c0a8125 --- /dev/null +++ b/internal/sql/ast/recursive_func_call.go @@ -0,0 +1,33 @@ +package ast + +type RecursiveFuncCall struct { + Func Node + Funcname *List + Args *List + AggOrder *List + AggFilter Node + AggWithinGroup bool + AggStar bool + AggDistinct bool + FuncVariadic bool + Over *WindowDef + Location int +} + +func (n *RecursiveFuncCall) Pos() int { + return n.Location +} + +func (n *RecursiveFuncCall) Format(buf *TrackedBuffer) { + if n == nil { + return + } + buf.astFormat(n.Func) + buf.WriteString("(") + if n.AggStar { + buf.WriteString("*") + } else { + buf.astFormat(n.Args) + } + buf.WriteString(")") +} diff --git a/internal/sql/ast/role_spec.go b/internal/sql/ast/role_spec.go index fba4cecf7d..5b7c871c54 100644 --- a/internal/sql/ast/role_spec.go +++ b/internal/sql/ast/role_spec.go @@ -4,6 +4,8 @@ type RoleSpec struct { Roletype RoleSpecType Rolename *string Location int + + BindRolename Node } func (n *RoleSpec) Pos() int { diff --git a/internal/sql/ast/update_stmt.go b/internal/sql/ast/update_stmt.go index efd496ad75..ea3e5bc65f 100644 --- a/internal/sql/ast/update_stmt.go +++ b/internal/sql/ast/update_stmt.go @@ -10,6 +10,11 @@ type UpdateStmt struct { LimitCount Node ReturningList *List WithClause *WithClause + + // YDB specific + Batch bool + OnCols *List + OnSelectStmt Node } func (n *UpdateStmt) Pos() int { @@ -25,6 +30,10 @@ func (n *UpdateStmt) Format(buf *TrackedBuffer) { buf.WriteString(" ") } + if n.Batch { + buf.WriteString("BATCH ") + } + buf.WriteString("UPDATE ") if items(n.Relations) { buf.astFormat(n.Relations) @@ -100,6 +109,20 @@ func (n *UpdateStmt) Format(buf *TrackedBuffer) { buf.astFormat(n.WhereClause) } + if items(n.OnCols) || set(n.OnSelectStmt) { + buf.WriteString(" ON ") + + if items(n.OnCols) { + buf.WriteString("(") + buf.astFormat(n.OnCols) + buf.WriteString(") ") + } + + if set(n.OnSelectStmt) { + buf.astFormat(n.OnSelectStmt) + } + } + if set(n.LimitCount) { buf.WriteString(" LIMIT ") buf.astFormat(n.LimitCount) diff --git a/internal/sql/ast/use_stmt.go b/internal/sql/ast/use_stmt.go new file mode 100644 index 0000000000..dee393c321 --- /dev/null +++ b/internal/sql/ast/use_stmt.go @@ -0,0 +1,11 @@ +package ast + +// YDB specific +type UseStmt struct { + Xpr Node + Location int +} + +func (n *UseStmt) Pos() int { + return n.Location +} diff --git a/internal/sql/astutils/rewrite.go b/internal/sql/astutils/rewrite.go index 93c5be3cfb..bcc7c17e40 100644 --- a/internal/sql/astutils/rewrite.go +++ b/internal/sql/astutils/rewrite.go @@ -605,6 +605,7 @@ func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast. a.apply(n, "Params", nil, n.Params) case *ast.CreateRoleStmt: + a.apply(n, "BindRole", nil, n.BindRole) a.apply(n, "Options", nil, n.Options) case *ast.CreateSchemaStmt: @@ -685,6 +686,8 @@ func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast. a.apply(n, "Relations", nil, n.Relations) a.apply(n, "UsingClause", nil, n.UsingClause) a.apply(n, "WhereClause", nil, n.WhereClause) + a.apply(n, "Cols", nil, n.OnCols) + a.apply(n, "SelectStmt", nil, n.OnSelectStmt) a.apply(n, "ReturningList", nil, n.ReturningList) a.apply(n, "WithClause", nil, n.WithClause) @@ -922,6 +925,11 @@ func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast. case *ast.PartitionSpec: a.apply(n, "PartParams", nil, n.PartParams) + case *ast.Pragma_stmt: + a.apply(n, "Pragma", nil, n.Name) + a.apply(n, "Args", nil, n.Cols) + a.apply(n, "Options", nil, n.Values) + case *ast.PrepareStmt: a.apply(n, "Argtypes", nil, n.Argtypes) a.apply(n, "Query", nil, n.Query) @@ -1005,6 +1013,14 @@ func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast. a.apply(n, "Roles", nil, n.Roles) a.apply(n, "Newrole", nil, n.Newrole) + case *ast.RecursiveFuncCall: + a.apply(n, "Func", nil, n.Func) + a.apply(n, "Funcname", nil, n.Funcname) + a.apply(n, "Args", nil, n.Args) + a.apply(n, "AggOrder", nil, n.AggOrder) + a.apply(n, "AggFilter", nil, n.AggFilter) + a.apply(n, "Over", nil, n.Over) + case *ast.RefreshMatViewStmt: a.apply(n, "Relation", nil, n.Relation) @@ -1027,7 +1043,7 @@ func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast. a.apply(n, "Val", nil, n.Val) case *ast.RoleSpec: - // pass + a.apply(n, "BindRolename", nil, n.BindRolename) case *ast.RowCompareExpr: a.apply(n, "Xpr", nil, n.Xpr) @@ -1159,9 +1175,14 @@ func (a *application) apply(parent ast.Node, name string, iter *iterator, n ast. a.apply(n, "TargetList", nil, n.TargetList) a.apply(n, "WhereClause", nil, n.WhereClause) a.apply(n, "FromClause", nil, n.FromClause) + a.apply(n, "Cols", nil, n.OnCols) + a.apply(n, "SelectStmt", nil, n.OnSelectStmt) a.apply(n, "ReturningList", nil, n.ReturningList) a.apply(n, "WithClause", nil, n.WithClause) + case *ast.UseStmt: + a.apply(n, "Xpr", nil, n.Xpr) + case *ast.VacuumStmt: a.apply(n, "Relation", nil, n.Relation) a.apply(n, "VaCols", nil, n.VaCols) diff --git a/internal/sql/astutils/walk.go b/internal/sql/astutils/walk.go index 0943379f03..dfc313fda1 100644 --- a/internal/sql/astutils/walk.go +++ b/internal/sql/astutils/walk.go @@ -898,6 +898,9 @@ func Walk(f Visitor, node ast.Node) { } case *ast.CreateRoleStmt: + if n.BindRole != nil { + Walk(f, n.BindRole) + } if n.Options != nil { Walk(f, n.Options) } @@ -1068,6 +1071,12 @@ func Walk(f Visitor, node ast.Node) { if n.WhereClause != nil { Walk(f, n.WhereClause) } + if n.OnCols != nil { + Walk(f, n.OnCols) + } + if n.OnSelectStmt != nil { + Walk(f, n.OnSelectStmt) + } if n.LimitCount != nil { Walk(f, n.LimitCount) } @@ -1510,6 +1519,17 @@ func Walk(f Visitor, node ast.Node) { Walk(f, n.PartParams) } + case *ast.Pragma_stmt: + if n.Name != nil { + Walk(f, n.Name) + } + if n.Cols != nil { + Walk(f, n.Cols) + } + if n.Values != nil { + Walk(f, n.Values) + } + case *ast.PrepareStmt: if n.Argtypes != nil { Walk(f, n.Argtypes) @@ -1714,6 +1734,26 @@ func Walk(f Visitor, node ast.Node) { Walk(f, n.Newrole) } + case *ast.RecursiveFuncCall: + if n.Func != nil { + Walk(f, n.Func) + } + if n.Funcname != nil { + Walk(f, n.Funcname) + } + if n.Args != nil { + Walk(f, n.Args) + } + if n.AggOrder != nil { + Walk(f, n.AggOrder) + } + if n.AggFilter != nil { + Walk(f, n.AggFilter) + } + if n.Over != nil { + Walk(f, n.Over) + } + case *ast.RefreshMatViewStmt: if n.Relation != nil { Walk(f, n.Relation) @@ -1752,7 +1792,9 @@ func Walk(f Visitor, node ast.Node) { } case *ast.RoleSpec: - // pass + if n.BindRolename != nil { + Walk(f, n.BindRolename) + } case *ast.RowCompareExpr: if n.Xpr != nil { @@ -2041,6 +2083,12 @@ func Walk(f Visitor, node ast.Node) { if n.FromClause != nil { Walk(f, n.FromClause) } + if n.OnCols != nil { + Walk(f, n.OnCols) + } + if n.OnSelectStmt != nil { + Walk(f, n.OnSelectStmt) + } if n.LimitCount != nil { Walk(f, n.LimitCount) } @@ -2051,6 +2099,11 @@ func Walk(f Visitor, node ast.Node) { Walk(f, n.WithClause) } + case *ast.UseStmt: + if n.Xpr != nil { + Walk(f, n.Xpr) + } + case *ast.VacuumStmt: if n.Relation != nil { Walk(f, n.Relation) diff --git a/internal/sql/rewrite/parameters.go b/internal/sql/rewrite/parameters.go index d1ea1a22cc..9146d17e08 100644 --- a/internal/sql/rewrite/parameters.go +++ b/internal/sql/rewrite/parameters.go @@ -172,6 +172,8 @@ func NamedParameters(engine config.Engine, raw *ast.RawStmt, numbs map[int]bool, replace = "?" } else if engine == config.EngineSQLite { replace = fmt.Sprintf("?%d", argn) + } else if engine == config.EngineYDB { + replace = fmt.Sprintf("$%s", paramName) } else { replace = fmt.Sprintf("$%d", argn) } diff --git a/internal/sqltest/local/ydb.go b/internal/sqltest/local/ydb.go new file mode 100644 index 0000000000..8703b170b5 --- /dev/null +++ b/internal/sqltest/local/ydb.go @@ -0,0 +1,117 @@ +package local + +import ( + "context" + "database/sql" + "fmt" + "hash/fnv" + "math/rand" + "net" + "os" + "testing" + "time" + + migrate "github.com/sqlc-dev/sqlc/internal/migrations" + "github.com/sqlc-dev/sqlc/internal/sql/sqlpath" + "github.com/ydb-platform/ydb-go-sdk/v3" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func YDB(t *testing.T, migrations []string) TestYDB { + return link_YDB(t, migrations, true) +} + +func ReadOnlyYDB(t *testing.T, migrations []string) TestYDB { + 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 { + t.Helper() + + time.Sleep(1 * time.Second) // wait for YDB to start + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + dbuiri := os.Getenv("YDB_SERVER_URI") + 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 == "" { + 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", 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, + ydb.WithInsecure(), + ydb.WithDiscoveryInterval(time.Hour), + ydb.WithNodeAddressMutator(func(_ string) string { + return host + }), + ) + if err != nil { + t.Fatalf("failed to open root YDB connection: %s", err) + } + + connector, err := ydb.Connector( + driver, + ydb.WithTablePathPrefix(prefix), + ydb.WithAutoDeclare(), + ydb.WithNumericArgs(), + ) + if err != nil { + t.Fatalf("failed to create connector: %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) + if err != nil { + t.Fatalf("failed to apply migration: %s\nSQL: %s", err, stmt) + } + } + return TestYDB{DB: db, Prefix: prefix} +}