From 67e7c375ab3c25e6403d12ed2ab7bbe0b0058b70 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 17:58:55 +0000 Subject: [PATCH 1/4] googlesql: convert ALTER TABLE, views, renames and HIDDEN columns ALTER TABLE was dropped as unsupported, so columns it added, dropped or retyped never reached the catalog. It now converts ADD/DROP COLUMN, Spanner's ALTER COLUMN (which restates the type and nullability), ALTER COLUMN ... DROP NOT NULL, RENAME COLUMN and RENAME TO; RENAME TABLE converts too. CREATE [OR REPLACE] VIEW and DROP VIEW convert, and a HIDDEN column is left out of SELECT * as Spanner does. The new googlesql cases' schemas were checked against Spanner Omni. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8QoGi6VRtADkb4jL3BFHP --- .../testdata/create_view/googlesql/exec.json | 3 + .../testdata/create_view/googlesql/go/db.go | 31 +++ .../create_view/googlesql/go/models.go | 15 ++ .../create_view/googlesql/go/query.sql.go | 44 +++++ .../testdata/create_view/googlesql/query.sql | 2 + .../testdata/create_view/googlesql/schema.sql | 11 ++ .../testdata/create_view/googlesql/sqlc.json | 12 ++ .../googlesql/exec.json | 3 + .../googlesql/go/db.go | 31 +++ .../googlesql/go/models.go | 16 ++ .../googlesql/go/query.sql.go | 42 ++++ .../googlesql/query.sql | 2 + .../googlesql/schema.sql | 6 + .../googlesql/sqlc.json | 12 ++ .../googlesql/exec.json | 3 + .../googlesql/go/db.go | 31 +++ .../googlesql/go/models.go | 16 ++ .../googlesql/go/query.sql.go | 42 ++++ .../googlesql/query.sql | 2 + .../googlesql/schema.sql | 9 + .../googlesql/sqlc.json | 12 ++ .../googlesql/exec.json | 3 + .../googlesql/go/db.go | 31 +++ .../googlesql/go/models.go | 14 ++ .../googlesql/go/query.sql.go | 37 ++++ .../googlesql/query.sql | 2 + .../googlesql/schema.sql | 6 + .../googlesql/sqlc.json | 12 ++ .../googlesql/exec.json | 3 + .../ddl_alter_table_rename/googlesql/go/db.go | 31 +++ .../googlesql/go/models.go | 13 ++ .../googlesql/go/query.sql.go | 64 ++++++ .../googlesql/query.sql | 5 + .../googlesql/schema.sql | 8 + .../googlesql/sqlc.json | 12 ++ .../testdata/ddl_spanner/googlesql/exec.json | 3 + .../testdata/ddl_spanner/googlesql/go/db.go | 31 +++ .../ddl_spanner/googlesql/go/models.go | 35 ++++ .../ddl_spanner/googlesql/go/query.sql.go | 109 +++++++++++ .../testdata/ddl_spanner/googlesql/query.sql | 8 + .../testdata/ddl_spanner/googlesql/schema.sql | 42 ++++ .../testdata/ddl_spanner/googlesql/sqlc.json | 12 ++ internal/engine/googlesql/convert.go | 183 +++++++++++++++++- 43 files changed, 1005 insertions(+), 4 deletions(-) create mode 100644 internal/endtoend/testdata/create_view/googlesql/exec.json create mode 100644 internal/endtoend/testdata/create_view/googlesql/go/db.go create mode 100644 internal/endtoend/testdata/create_view/googlesql/go/models.go create mode 100644 internal/endtoend/testdata/create_view/googlesql/go/query.sql.go create mode 100644 internal/endtoend/testdata/create_view/googlesql/query.sql create mode 100644 internal/endtoend/testdata/create_view/googlesql/schema.sql create mode 100644 internal/endtoend/testdata/create_view/googlesql/sqlc.json create mode 100644 internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/exec.json create mode 100644 internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/db.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/models.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/query.sql.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/query.sql create mode 100644 internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/schema.sql create mode 100644 internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/sqlc.json create mode 100644 internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/exec.json create mode 100644 internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/db.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/models.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/query.sql.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/query.sql create mode 100644 internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/schema.sql create mode 100644 internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/sqlc.json create mode 100644 internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/exec.json create mode 100644 internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/db.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/models.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/query.sql.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/query.sql create mode 100644 internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/schema.sql create mode 100644 internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/sqlc.json create mode 100644 internal/endtoend/testdata/ddl_alter_table_rename/googlesql/exec.json create mode 100644 internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/db.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/models.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/query.sql.go create mode 100644 internal/endtoend/testdata/ddl_alter_table_rename/googlesql/query.sql create mode 100644 internal/endtoend/testdata/ddl_alter_table_rename/googlesql/schema.sql create mode 100644 internal/endtoend/testdata/ddl_alter_table_rename/googlesql/sqlc.json create mode 100644 internal/endtoend/testdata/ddl_spanner/googlesql/exec.json create mode 100644 internal/endtoend/testdata/ddl_spanner/googlesql/go/db.go create mode 100644 internal/endtoend/testdata/ddl_spanner/googlesql/go/models.go create mode 100644 internal/endtoend/testdata/ddl_spanner/googlesql/go/query.sql.go create mode 100644 internal/endtoend/testdata/ddl_spanner/googlesql/query.sql create mode 100644 internal/endtoend/testdata/ddl_spanner/googlesql/schema.sql create mode 100644 internal/endtoend/testdata/ddl_spanner/googlesql/sqlc.json diff --git a/internal/endtoend/testdata/create_view/googlesql/exec.json b/internal/endtoend/testdata/create_view/googlesql/exec.json new file mode 100644 index 0000000000..2e996ca79d --- /dev/null +++ b/internal/endtoend/testdata/create_view/googlesql/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/create_view/googlesql/go/db.go b/internal/endtoend/testdata/create_view/googlesql/go/db.go new file mode 100644 index 0000000000..32faa9f2b8 --- /dev/null +++ b/internal/endtoend/testdata/create_view/googlesql/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +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/internal/endtoend/testdata/create_view/googlesql/go/models.go b/internal/endtoend/testdata/create_view/googlesql/go/models.go new file mode 100644 index 0000000000..522a07f37d --- /dev/null +++ b/internal/endtoend/testdata/create_view/googlesql/go/models.go @@ -0,0 +1,15 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Venue struct { + ID int64 + Name string + City sql.NullString +} diff --git a/internal/endtoend/testdata/create_view/googlesql/go/query.sql.go b/internal/endtoend/testdata/create_view/googlesql/go/query.sql.go new file mode 100644 index 0000000000..da7ee04904 --- /dev/null +++ b/internal/endtoend/testdata/create_view/googlesql/go/query.sql.go @@ -0,0 +1,44 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const listVenueNames = `-- name: ListVenueNames :many +SELECT id, name, city FROM venue_names +` + +type ListVenueNamesRow struct { + ID int64 + Name string + City sql.NullString +} + +func (q *Queries) ListVenueNames(ctx context.Context) ([]ListVenueNamesRow, error) { + rows, err := q.db.QueryContext(ctx, listVenueNames) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListVenueNamesRow + for rows.Next() { + var i ListVenueNamesRow + if err := rows.Scan(&i.ID, &i.Name, &i.City); 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 +} diff --git a/internal/endtoend/testdata/create_view/googlesql/query.sql b/internal/endtoend/testdata/create_view/googlesql/query.sql new file mode 100644 index 0000000000..c82210095d --- /dev/null +++ b/internal/endtoend/testdata/create_view/googlesql/query.sql @@ -0,0 +1,2 @@ +-- name: ListVenueNames :many +SELECT * FROM venue_names; diff --git a/internal/endtoend/testdata/create_view/googlesql/schema.sql b/internal/endtoend/testdata/create_view/googlesql/schema.sql new file mode 100644 index 0000000000..6919db659e --- /dev/null +++ b/internal/endtoend/testdata/create_view/googlesql/schema.sql @@ -0,0 +1,11 @@ +CREATE TABLE venues ( + id INT64 NOT NULL, + name STRING(MAX) NOT NULL, + city STRING(MAX), +) PRIMARY KEY (id); +CREATE VIEW venue_names SQL SECURITY INVOKER AS +SELECT venues.id, venues.name FROM venues; +CREATE OR REPLACE VIEW venue_names SQL SECURITY INVOKER AS +SELECT venues.id, venues.name, venues.city FROM venues; +CREATE VIEW dropped SQL SECURITY INVOKER AS SELECT venues.id FROM venues; +DROP VIEW dropped; diff --git a/internal/endtoend/testdata/create_view/googlesql/sqlc.json b/internal/endtoend/testdata/create_view/googlesql/sqlc.json new file mode 100644 index 0000000000..40d6c76211 --- /dev/null +++ b/internal/endtoend/testdata/create_view/googlesql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "googlesql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/exec.json b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/exec.json new file mode 100644 index 0000000000..2e996ca79d --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/db.go b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/db.go new file mode 100644 index 0000000000..32faa9f2b8 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +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/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/models.go b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/models.go new file mode 100644 index 0000000000..36f498a442 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/models.go @@ -0,0 +1,16 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Venue struct { + ID int64 + Name sql.NullString + Location sql.NullString + Size int64 +} diff --git a/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/query.sql.go b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/query.sql.go new file mode 100644 index 0000000000..a197546f8a --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/go/query.sql.go @@ -0,0 +1,42 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" +) + +const placeholder = `-- name: Placeholder :many +SELECT id, name, location, size FROM venues +` + +func (q *Queries) Placeholder(ctx context.Context) ([]Venue, error) { + rows, err := q.db.QueryContext(ctx, placeholder) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Venue + for rows.Next() { + var i Venue + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Location, + &i.Size, + ); 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 +} diff --git a/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/query.sql b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/query.sql new file mode 100644 index 0000000000..3564511e10 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/query.sql @@ -0,0 +1,2 @@ +-- name: Placeholder :many +SELECT * FROM venues; diff --git a/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/schema.sql b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/schema.sql new file mode 100644 index 0000000000..d4a74b1bf5 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE venues ( + id INT64 NOT NULL, + name STRING(MAX), +) PRIMARY KEY (id); +ALTER TABLE venues ADD COLUMN location STRING(MAX); +ALTER TABLE venues ADD COLUMN IF NOT EXISTS size INT64 NOT NULL DEFAULT (0); diff --git a/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/sqlc.json b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/sqlc.json new file mode 100644 index 0000000000..40d6c76211 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_add_column/googlesql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "googlesql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/exec.json b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/exec.json new file mode 100644 index 0000000000..2e996ca79d --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/db.go b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/db.go new file mode 100644 index 0000000000..32faa9f2b8 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +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/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/models.go b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/models.go new file mode 100644 index 0000000000..8e18cbc391 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/models.go @@ -0,0 +1,16 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Venue struct { + ID int64 + Name string + Capacity int64 + Notes sql.NullString +} diff --git a/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/query.sql.go b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/query.sql.go new file mode 100644 index 0000000000..92690255c1 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/go/query.sql.go @@ -0,0 +1,42 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" +) + +const placeholder = `-- name: Placeholder :many +SELECT id, name, capacity, notes FROM venues +` + +func (q *Queries) Placeholder(ctx context.Context) ([]Venue, error) { + rows, err := q.db.QueryContext(ctx, placeholder) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Venue + for rows.Next() { + var i Venue + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Capacity, + &i.Notes, + ); 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 +} diff --git a/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/query.sql b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/query.sql new file mode 100644 index 0000000000..3564511e10 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/query.sql @@ -0,0 +1,2 @@ +-- name: Placeholder :many +SELECT * FROM venues; diff --git a/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/schema.sql b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/schema.sql new file mode 100644 index 0000000000..3571469d9e --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE venues ( + id INT64 NOT NULL, + name STRING(10) NOT NULL, + capacity INT64, + notes STRING(MAX) NOT NULL, +) PRIMARY KEY (id); +ALTER TABLE venues ALTER COLUMN name STRING(MAX) NOT NULL; +ALTER TABLE venues ALTER COLUMN capacity INT64 NOT NULL DEFAULT (0); +ALTER TABLE venues ALTER COLUMN notes STRING(MAX); diff --git a/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/sqlc.json b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/sqlc.json new file mode 100644 index 0000000000..40d6c76211 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_alter_type/googlesql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "googlesql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/exec.json b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/exec.json new file mode 100644 index 0000000000..2e996ca79d --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/db.go b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/db.go new file mode 100644 index 0000000000..32faa9f2b8 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +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/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/models.go b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/models.go new file mode 100644 index 0000000000..c67630d1c3 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/models.go @@ -0,0 +1,14 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Venue struct { + ID int64 + Name sql.NullString +} diff --git a/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/query.sql.go b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/query.sql.go new file mode 100644 index 0000000000..d0eefdd98e --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/go/query.sql.go @@ -0,0 +1,37 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" +) + +const placeholder = `-- name: Placeholder :many +SELECT id, name FROM venues +` + +func (q *Queries) Placeholder(ctx context.Context) ([]Venue, error) { + rows, err := q.db.QueryContext(ctx, placeholder) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Venue + for rows.Next() { + var i Venue + if err := rows.Scan(&i.ID, &i.Name); 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 +} diff --git a/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/query.sql b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/query.sql new file mode 100644 index 0000000000..3564511e10 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/query.sql @@ -0,0 +1,2 @@ +-- name: Placeholder :many +SELECT * FROM venues; diff --git a/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/schema.sql b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/schema.sql new file mode 100644 index 0000000000..6cd6d76fff --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE venues ( + id INT64 NOT NULL, + name STRING(MAX), + slug STRING(MAX), +) PRIMARY KEY (id); +ALTER TABLE venues DROP COLUMN slug; diff --git a/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/sqlc.json b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/sqlc.json new file mode 100644 index 0000000000..40d6c76211 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_drop_column/googlesql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "googlesql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/exec.json b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/exec.json new file mode 100644 index 0000000000..2e996ca79d --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/db.go b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/db.go new file mode 100644 index 0000000000..32faa9f2b8 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +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/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/models.go b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/models.go new file mode 100644 index 0000000000..b6eccd45e1 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/models.go @@ -0,0 +1,13 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +type Arena struct { + ID int64 +} + +type Venue struct { + ID int64 +} diff --git a/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/query.sql.go b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/query.sql.go new file mode 100644 index 0000000000..ef6ecead1f --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/go/query.sql.go @@ -0,0 +1,64 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" +) + +const listArenas = `-- name: ListArenas :many +SELECT id FROM arena +` + +func (q *Queries) ListArenas(ctx context.Context) ([]int64, error) { + rows, err := q.db.QueryContext(ctx, listArenas) + if err != nil { + return nil, err + } + defer rows.Close() + var items []int64 + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + items = append(items, id) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const listVenues = `-- name: ListVenues :many +SELECT id FROM venue +` + +func (q *Queries) ListVenues(ctx context.Context) ([]int64, error) { + rows, err := q.db.QueryContext(ctx, listVenues) + if err != nil { + return nil, err + } + defer rows.Close() + var items []int64 + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + items = append(items, id) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/query.sql b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/query.sql new file mode 100644 index 0000000000..274ee7b250 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/query.sql @@ -0,0 +1,5 @@ +-- name: ListVenues :many +SELECT * FROM venue; + +-- name: ListArenas :many +SELECT * FROM arena; diff --git a/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/schema.sql b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/schema.sql new file mode 100644 index 0000000000..d6da5181dd --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE venues ( + id INT64 NOT NULL, +) PRIMARY KEY (id); +CREATE TABLE arenas ( + id INT64 NOT NULL, +) PRIMARY KEY (id); +ALTER TABLE venues RENAME TO venue; +RENAME TABLE arenas TO stadiums, stadiums TO arena; diff --git a/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/sqlc.json b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/sqlc.json new file mode 100644 index 0000000000..40d6c76211 --- /dev/null +++ b/internal/endtoend/testdata/ddl_alter_table_rename/googlesql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "googlesql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/ddl_spanner/googlesql/exec.json b/internal/endtoend/testdata/ddl_spanner/googlesql/exec.json new file mode 100644 index 0000000000..2e996ca79d --- /dev/null +++ b/internal/endtoend/testdata/ddl_spanner/googlesql/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/ddl_spanner/googlesql/go/db.go b/internal/endtoend/testdata/ddl_spanner/googlesql/go/db.go new file mode 100644 index 0000000000..32faa9f2b8 --- /dev/null +++ b/internal/endtoend/testdata/ddl_spanner/googlesql/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +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/internal/endtoend/testdata/ddl_spanner/googlesql/go/models.go b/internal/endtoend/testdata/ddl_spanner/googlesql/go/models.go new file mode 100644 index 0000000000..973faeb46e --- /dev/null +++ b/internal/endtoend/testdata/ddl_spanner/googlesql/go/models.go @@ -0,0 +1,35 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" + "time" + + "cloud.google.com/go/spanner" +) + +type Album struct { + SingerID int64 + AlbumID int64 + Title sql.NullString + CreatedAt sql.NullTime +} + +type Singer struct { + SingerID int64 + FirstName sql.NullString + LastName string + FullName string + Active bool + UpdatedAt time.Time +} + +type Ticket struct { + TicketID int64 + SingerID int64 + SeatID sql.NullInt64 + Embedding []spanner.NullFloat32 +} diff --git a/internal/endtoend/testdata/ddl_spanner/googlesql/go/query.sql.go b/internal/endtoend/testdata/ddl_spanner/googlesql/go/query.sql.go new file mode 100644 index 0000000000..34ff922f61 --- /dev/null +++ b/internal/endtoend/testdata/ddl_spanner/googlesql/go/query.sql.go @@ -0,0 +1,109 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const listAlbums = `-- name: ListAlbums :many +SELECT singer_id, album_id, title, created_at FROM albums WHERE singer_id = @singer_id +` + +func (q *Queries) ListAlbums(ctx context.Context, singerID int64) ([]Album, error) { + rows, err := q.db.QueryContext(ctx, listAlbums, sql.Named("singer_id", singerID)) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Album + for rows.Next() { + var i Album + if err := rows.Scan( + &i.SingerID, + &i.AlbumID, + &i.Title, + &i.CreatedAt, + ); 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 listSingers = `-- name: ListSingers :many +SELECT singer_id, first_name, last_name, full_name, active, updated_at FROM singers +` + +func (q *Queries) ListSingers(ctx context.Context) ([]Singer, error) { + rows, err := q.db.QueryContext(ctx, listSingers) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Singer + for rows.Next() { + var i Singer + if err := rows.Scan( + &i.SingerID, + &i.FirstName, + &i.LastName, + &i.FullName, + &i.Active, + &i.UpdatedAt, + ); 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 listTickets = `-- name: ListTickets :many +SELECT ticket_id, singer_id, seat_id, embedding FROM tickets +` + +func (q *Queries) ListTickets(ctx context.Context) ([]Ticket, error) { + rows, err := q.db.QueryContext(ctx, listTickets) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Ticket + for rows.Next() { + var i Ticket + if err := rows.Scan( + &i.TicketID, + &i.SingerID, + &i.SeatID, + &i.Embedding, + ); 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 +} diff --git a/internal/endtoend/testdata/ddl_spanner/googlesql/query.sql b/internal/endtoend/testdata/ddl_spanner/googlesql/query.sql new file mode 100644 index 0000000000..c42d016638 --- /dev/null +++ b/internal/endtoend/testdata/ddl_spanner/googlesql/query.sql @@ -0,0 +1,8 @@ +-- name: ListSingers :many +SELECT * FROM singers; + +-- name: ListAlbums :many +SELECT * FROM albums WHERE singer_id = @singer_id; + +-- name: ListTickets :many +SELECT * FROM tickets; diff --git a/internal/endtoend/testdata/ddl_spanner/googlesql/schema.sql b/internal/endtoend/testdata/ddl_spanner/googlesql/schema.sql new file mode 100644 index 0000000000..fb0203267e --- /dev/null +++ b/internal/endtoend/testdata/ddl_spanner/googlesql/schema.sql @@ -0,0 +1,42 @@ +CREATE SEQUENCE ticket_ids OPTIONS (sequence_kind = 'bit_reversed_positive'); + +CREATE TABLE singers ( + singer_id INT64 NOT NULL, + first_name STRING(1024), + last_name STRING(1024) NOT NULL, + full_name STRING(2048) NOT NULL AS (ARRAY_TO_STRING([first_name, last_name], ' ')) STORED, + active BOOL NOT NULL DEFAULT (true), + updated_at TIMESTAMP NOT NULL OPTIONS (allow_commit_timestamp = true), + name_tokens TOKENLIST AS (TOKENIZE_FULLTEXT(last_name)) HIDDEN, +) PRIMARY KEY (singer_id); + +CREATE TABLE albums ( + singer_id INT64 NOT NULL, + album_id INT64 NOT NULL, + title STRING(MAX), + created_at TIMESTAMP, + CONSTRAINT title_not_empty CHECK (title != ''), +) PRIMARY KEY (singer_id, album_id), + INTERLEAVE IN PARENT singers ON DELETE CASCADE, + ROW DELETION POLICY (OLDER_THAN(created_at, INTERVAL 30 DAY)); + +CREATE TABLE tickets ( + ticket_id INT64 NOT NULL DEFAULT (GET_NEXT_SEQUENCE_VALUE(SEQUENCE ticket_ids)), + singer_id INT64 NOT NULL, + seat_id INT64 GENERATED BY DEFAULT AS IDENTITY (BIT_REVERSED_POSITIVE), + embedding ARRAY(vector_length=>3), + CONSTRAINT fk_singer FOREIGN KEY (singer_id) REFERENCES singers (singer_id), +) PRIMARY KEY (ticket_id); + +CREATE INDEX albums_by_title ON albums(title); +CREATE UNIQUE NULL_FILTERED INDEX singers_by_last_name ON singers(last_name) STORING (first_name); +CREATE SEARCH INDEX singers_by_name_tokens ON singers(name_tokens); +CREATE VECTOR INDEX tickets_by_embedding ON tickets(embedding) WHERE embedding IS NOT NULL OPTIONS (distance_type = 'COSINE'); + +CREATE CHANGE STREAM everything FOR ALL; +CREATE CHANGE STREAM singer_changes FOR singers(first_name, last_name) OPTIONS (retention_period = '7d'); + +CREATE ROLE reader; +GRANT SELECT ON TABLE singers, albums TO ROLE reader; + +ALTER TABLE albums REPLACE ROW DELETION POLICY (OLDER_THAN(created_at, INTERVAL 60 DAY)); diff --git a/internal/endtoend/testdata/ddl_spanner/googlesql/sqlc.json b/internal/endtoend/testdata/ddl_spanner/googlesql/sqlc.json new file mode 100644 index 0000000000..40d6c76211 --- /dev/null +++ b/internal/endtoend/testdata/ddl_spanner/googlesql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "googlesql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/engine/googlesql/convert.go b/internal/engine/googlesql/convert.go index abce8d5bdf..fc4f5f6327 100644 --- a/internal/engine/googlesql/convert.go +++ b/internal/engine/googlesql/convert.go @@ -28,6 +28,12 @@ func (c *cc) convert(node zjast.Node) ast.Node { return c.convertDeleteStatement(n) case *zjast.CreateTableStatement: return c.convertCreateTableStatement(n) + case *zjast.CreateViewStatement: + return c.convertCreateViewStatement(n) + case *zjast.AlterStatement: + return c.convertAlterStatement(n) + case *zjast.RenameStatement: + return c.convertRenameStatement(n) case *zjast.DropStatement: return c.convertDropStatement(n) case *zjast.TruncateStatement: @@ -932,12 +938,17 @@ func (c *cc) convertColumnDefinition(n *zjast.ColumnDefinition) *ast.ColumnDef { col.IsNotNull = true case *zjast.PrimaryKeyColumnAttribute: col.PrimaryKey = true + case *zjast.HiddenColumnAttribute: + // Spanner leaves a HIDDEN column out of SELECT *. + col.IsHidden = true } } } if simple, ok := n.Schema.(*zjast.SimpleColumnSchema); ok { - if simple.DefaultExpression != nil { + // A generated column's "AS (expression) STORED" and an identity + // column's IDENTITY(...) arrive here too; neither is a default. + if _, generated := simple.DefaultExpression.(*zjast.GeneratedColumnInfo); !generated && simple.DefaultExpression != nil { col.RawDefault = c.convertExpr(simple.DefaultExpression) } } @@ -945,10 +956,174 @@ func (c *cc) convertColumnDefinition(n *zjast.ColumnDefinition) *ast.ColumnDef { return col } +// convertCreateViewStatement converts CREATE [OR REPLACE] VIEW. Spanner's +// SQL SECURITY clause says nothing about the view's columns. +func (c *cc) convertCreateViewStatement(n *zjast.CreateViewStatement) ast.Node { + if n.ViewKind != "" || n.Query == nil || n.Name == nil { + return todo(n) + } + stmt := &ast.ViewStmt{ + View: parseRangeVar(n.Name), + Query: c.convertQuery(n.Query), + Replace: n.IsOrReplace, + } + if n.Columns != nil { + stmt.Aliases = &ast.List{} + for _, col := range n.Columns.Columns { + if col == nil || col.Name == nil { + continue + } + stmt.Aliases.Items = append(stmt.Aliases.Items, NewIdentifier(col.Name.Name)) + } + } + return stmt +} + +// convertAlterStatement converts ALTER TABLE. Its column actions become one +// AlterTableStmt; RENAME COLUMN and RENAME TO, which the sqlc AST spells as +// statements of their own, follow it in a list. Actions that change nothing +// about a column's name, type or nullability, such as constraints, options, +// defaults and row deletion policies, are dropped. +func (c *cc) convertAlterStatement(n *zjast.AlterStatement) ast.Node { + if n.NodeName != "AlterTableStatement" || n.Path == nil || n.Actions == nil { + return todo(n) + } + table := parseTableName(n.Path) + alter := &ast.AlterTableStmt{ + Table: table, + Cmds: &ast.List{}, + MissingOk: n.IsIfExists, + } + stmts := []ast.Node{alter} + for _, action := range n.Actions.Actions { + switch a := action.(type) { + case *zjast.AddColumnAction: + if a.Column == nil { + continue + } + def := c.convertColumnDefinition(a.Column) + alter.Cmds.Items = append(alter.Cmds.Items, &ast.AlterTableCmd{ + Name: &def.Colname, + Subtype: ast.AT_AddColumn, + Def: def, + MissingOk: a.IsIfNotExists, + }) + case *zjast.DropColumnAction: + if a.Name == nil { + continue + } + name := identifier(a.Name.Name) + alter.Cmds.Items = append(alter.Cmds.Items, &ast.AlterTableCmd{ + Name: &name, + Subtype: ast.AT_DropColumn, + MissingOk: a.IsIfExists, + }) + case *zjast.SpannerAlterColumnAction: + // Spanner's "ALTER COLUMN c [NOT NULL] ..." restates the + // column's whole definition, nullability included. + if a.Column == nil { + continue + } + def := c.convertColumnDefinition(a.Column) + alter.Cmds.Items = append(alter.Cmds.Items, &ast.AlterTableCmd{ + Name: &def.Colname, + Subtype: ast.AT_AlterColumnType, + Def: def, + }) + if !def.IsNotNull { + alter.Cmds.Items = append(alter.Cmds.Items, &ast.AlterTableCmd{ + Name: &def.Colname, + Subtype: ast.AT_DropNotNull, + }) + } + case *zjast.AlterColumnTypeAction: + if a.Column == nil { + continue + } + name := identifier(a.Column.Name) + alter.Cmds.Items = append(alter.Cmds.Items, &ast.AlterTableCmd{ + Name: &name, + Subtype: ast.AT_AlterColumnType, + MissingOk: a.IsIfExists, + Def: &ast.ColumnDef{ + Colname: name, + TypeName: spelledTypeName(columnSchemaTypeName(a.Schema)), + }, + }) + case *zjast.AlterColumnDropNotNullAction: + if a.Column == nil { + continue + } + name := identifier(a.Column.Name) + alter.Cmds.Items = append(alter.Cmds.Items, &ast.AlterTableCmd{ + Name: &name, + Subtype: ast.AT_DropNotNull, + MissingOk: a.IsIfExists, + }) + case *zjast.RenameColumnAction: + if a.Name == nil || a.NewName == nil { + continue + } + newName := identifier(a.NewName.Name) + stmts = append(stmts, &ast.RenameColumnStmt{ + Table: table, + Col: &ast.ColumnRef{Name: identifier(a.Name.Name)}, + NewName: &newName, + MissingOk: n.IsIfExists, + }) + case *zjast.RenameToClause: + parts := pathParts(a.NewName) + if len(parts) == 0 { + continue + } + newName := identifier(parts[len(parts)-1]) + stmts = append(stmts, &ast.RenameTableStmt{ + Table: table, + NewName: &newName, + MissingOk: n.IsIfExists, + }) + } + } + if len(stmts) == 1 { + return alter + } + return &ast.List{Items: stmts} +} + +// convertRenameStatement converts Spanner's "RENAME TABLE a TO b [, c TO +// d ...]", which renames each table in turn. +func (c *cc) convertRenameStatement(n *zjast.RenameStatement) ast.Node { + if n.Identifier == nil || !strings.EqualFold(n.Identifier.Name, "TABLE") { + return todo(n) + } + list := &ast.List{} + add := func(oldName, newName *zjast.PathExpression) { + parts := pathParts(newName) + if oldName == nil || len(parts) == 0 { + return + } + name := identifier(parts[len(parts)-1]) + list.Items = append(list.Items, &ast.RenameTableStmt{ + Table: parseTableName(oldName), + NewName: &name, + }) + } + add(n.OldName, n.NewName) + for _, more := range n.MoreRenames { + add(more.OldName, more.NewName) + } + if len(list.Items) == 0 { + return todo(n) + } + return list +} + func (c *cc) convertDropStatement(n *zjast.DropStatement) ast.Node { - // Only DROP TABLE maps onto the sqlc AST; other object kinds (views, - // functions, schemas, ...) fall through to TODO. - if strings.EqualFold(n.ObjectKind, "TABLE") && n.Path != nil { + // DROP TABLE and DROP VIEW map onto the sqlc AST, which drops both + // kinds of relation alike; other object kinds (indexes, sequences, + // schemas, ...) fall through to TODO. + kind := strings.ToUpper(n.ObjectKind) + if (kind == "TABLE" || kind == "VIEW") && n.Path != nil { return &ast.DropTableStmt{ IfExists: n.IsIfExists, Tables: []*ast.TableName{parseTableName(n.Path)}, From 24a9256ee5053c0aee48e9615e2abff735a285c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 18:07:53 +0000 Subject: [PATCH 2/4] googlesql: type arrays, UNNEST, subscripts and typed literals The converter now handles typed literals (DATE, TIMESTAMP, NUMERIC, JSON, bytes), array constructors, INTERVAL, the OFFSET/ORDINAL/SAFE_* subscripts and "x IN UNNEST(array)", which becomes "x = ANY(array)". In the analysis core: - A subscript is the array's element, nullable, with an integer index. - ARRAY(subquery) is an array of the column, not its first value. - "x = ANY(array)" types a placeholder as an array of x's type, as PostgreSQL does; sqlc.slice() still gets the element. - A polymorphic function taking anyarray and returning anyelement, as unnest does, returns the array's element, and a function in FROM takes that resolved type, with its alias naming the column. GoogleSQL gains an unnest seed. analyze_arrays is checked against Spanner Omni; the rest are in array_unnest, where Spanner's wire types lose the nullability and lengths the golden pins. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8QoGi6VRtADkb4jL3BFHP --- internal/compiler/parse_core.go | 8 + internal/core/analyzer/expr.go | 92 ++++++++++- internal/core/analyzer/scope.go | 23 ++- .../analyze_arrays/googlesql/exec.json | 5 + .../analyze_arrays/googlesql/query.sql | 8 + .../analyze_arrays/googlesql/schema.sql | 13 ++ .../analyze_arrays/googlesql/stdout.json | 109 +++++++++++++ .../analyze_types/bigquery/stdout.json | 12 +- .../testdata/array_unnest/googlesql/exec.json | 3 + .../testdata/array_unnest/googlesql/go/db.go | 31 ++++ .../array_unnest/googlesql/go/models.go | 24 +++ .../array_unnest/googlesql/go/query.sql.go | 144 ++++++++++++++++++ .../testdata/array_unnest/googlesql/query.sql | 16 ++ .../array_unnest/googlesql/schema.sql | 13 ++ .../testdata/array_unnest/googlesql/sqlc.json | 12 ++ internal/engine/googlesql/convert.go | 100 +++++++++++- .../engine/googlesql/dialect/functions.jsonl | 1 + 17 files changed, 602 insertions(+), 12 deletions(-) create mode 100644 internal/endtoend/testdata/analyze_arrays/googlesql/exec.json create mode 100644 internal/endtoend/testdata/analyze_arrays/googlesql/query.sql create mode 100644 internal/endtoend/testdata/analyze_arrays/googlesql/schema.sql create mode 100644 internal/endtoend/testdata/analyze_arrays/googlesql/stdout.json create mode 100644 internal/endtoend/testdata/array_unnest/googlesql/exec.json create mode 100644 internal/endtoend/testdata/array_unnest/googlesql/go/db.go create mode 100644 internal/endtoend/testdata/array_unnest/googlesql/go/models.go create mode 100644 internal/endtoend/testdata/array_unnest/googlesql/go/query.sql.go create mode 100644 internal/endtoend/testdata/array_unnest/googlesql/query.sql create mode 100644 internal/endtoend/testdata/array_unnest/googlesql/schema.sql create mode 100644 internal/endtoend/testdata/array_unnest/googlesql/sqlc.json diff --git a/internal/compiler/parse_core.go b/internal/compiler/parse_core.go index 543ca5f3cc..2e1dfc7042 100644 --- a/internal/compiler/parse_core.go +++ b/internal/compiler/parse_core.go @@ -201,5 +201,13 @@ func coreParamColumn(p core.Parameter, params *named.ParamSet) *Column { col.IsSqlcSlice = param.IsSqlcSlice() col.IsNamedParam = true } + // sqlc.slice() already says the argument is a list of values, so a + // slice the analyzer typed as an array, as it does "= ANY(sqlc.slice(x))", + // is a slice of the array's elements. + if col.IsSqlcSlice && col.TypeExpr.IsArray() { + col.TypeExpr = col.TypeExpr.Element() + col.IsArray = false + describeType(col, col.TypeExpr) + } return col } diff --git a/internal/core/analyzer/expr.go b/internal/core/analyzer/expr.go index 4f0dd8ec8b..8b7b716a75 100644 --- a/internal/core/analyzer/expr.go +++ b/internal/core/analyzer/expr.go @@ -79,6 +79,9 @@ func (a *analyzer) typeExpr(n ast.Node) (exprType, error) { case *ast.A_ArrayExpr: return a.typeArrayExpr(e) + case *ast.A_Indirection: + return a.typeIndirection(e) + case *ast.SubLink: return a.typeSubLink(e) @@ -366,7 +369,17 @@ func (a *analyzer) typeQuantifiedExpr(e *ast.A_Expr) (exprType, error) { if err != nil { return exprType{}, err } - if err := a.typeOperands(e.Rexpr, leftT); err != nil { + // "x = ANY(SELECT ...)" compares x against the subquery's column, but + // "x = ANY(array)" against the elements of an array, so a placeholder + // there is an array of x's type. + right := leftT + if _, sub := e.Rexpr.(*ast.SubLink); !sub { + right = a.arrayOf(leftT) + right.sourceClassOID = leftT.sourceClassOID + right.sourceAttributeOID = leftT.sourceAttributeOID + right.sourceTableAlias = leftT.sourceTableAlias + } + if err := a.typeOperands(e.Rexpr, right); err != nil { return exprType{}, err } return a.boolType(false) @@ -524,6 +537,51 @@ func (a *analyzer) typeArrayExpr(e *ast.A_ArrayExpr) (exprType, error) { return a.lookupType(core.Array(element.WithNullable(false))), nil } +// typeIndirection types an array subscript, arr[i], as the array's element, +// and a slice, arr[i:j], as the array itself. An element may be NULL, and a +// subscript past the end of the array yields NULL in the dialects that do not +// raise an error for it. The subscripts are integers. +func (a *analyzer) typeIndirection(e *ast.A_Indirection) (exprType, error) { + t, err := a.typeExpr(e.Arg) + if err != nil { + return exprType{}, err + } + intT := exprType{} + if oid, err := a.cat.ConstTypeOID(core.ConstInteger); err == nil { + intT.typeOID = oid + } + for _, item := range listItems(e.Indirection) { + idx, ok := item.(*ast.A_Indices) + if !ok { + return exprType{}, fmt.Errorf("typeExpr: unsupported indirection %T", item) + } + for _, bound := range []ast.Node{idx.Lidx, idx.Uidx} { + if err := a.typeOperands(bound, intT); err != nil { + return exprType{}, err + } + } + if idx.IsSlice { + continue + } + element := a.exprOf(t).Element() + if element == nil { + return exprType{nullable: true}, nil + } + t = a.lookupType(element) + t.nullable = true + } + return t, nil +} + +// arrayOf is the type of an array of t's type. +func (a *analyzer) arrayOf(t exprType) exprType { + element := a.exprOf(t) + if element == nil { + return exprType{} + } + return a.lookupType(core.Array(element.WithNullable(false))) +} + // lookupType is the type an expression refers to: its row when the catalog // holds one, its family's row when it holds only that, and the expression // alone when it holds neither. @@ -619,7 +677,7 @@ func (a *analyzer) typeSubLink(e *ast.SubLink) (exprType, error) { return exprType{}, err } switch e.SubLinkType { - case ast.EXPR_SUBLINK, ast.ARRAY_SUBLINK: + case ast.EXPR_SUBLINK: if len(cols) == 0 { return exprType{nullable: true}, nil } @@ -627,6 +685,13 @@ func (a *analyzer) typeSubLink(e *ast.SubLink) (exprType, error) { t := columnExprType(cols[0]) t.nullable = true return t, nil + case ast.ARRAY_SUBLINK: + // ARRAY(SELECT ...) collects the column into an array, which is + // empty rather than NULL when no row matches. + if len(cols) == 0 { + return exprType{}, nil + } + return a.arrayOf(columnExprType(cols[0])), nil default: return a.boolType(false) } @@ -916,11 +981,32 @@ func (a *analyzer) returnType(p core.ProcOverload, argTypes []exprType) exprType return exprType{} } if isPolymorphic(name) && (argTypes[0].typeOID != 0 || argTypes[0].expr != nil) { - return exprType{typeOID: argTypes[0].typeOID, expr: argTypes[0].expr} + arg := exprType{typeOID: argTypes[0].typeOID, expr: argTypes[0].expr} + // An element taken from an array argument, unnest(anyarray) + // returning anyelement, is the array's element; an array built from + // an element argument is an array of it. + paramName := "" + if len(p.ArgTypes) > 0 { + paramName, _ = a.cat.TypeName(p.ArgTypes[0]) + } + switch { + case isPolymorphicArray(paramName) && !isPolymorphicArray(name): + if element := a.exprOf(arg).Element(); element != nil { + return a.lookupType(element) + } + case !isPolymorphicArray(paramName) && isPolymorphicArray(name) && isPolymorphic(paramName): + return a.arrayOf(arg) + } + return arg } return exprType{typeOID: p.ReturnTypeOID} } +// isPolymorphicArray reports whether a polymorphic type stands for an array. +func isPolymorphicArray(typeName string) bool { + return typeName == "anyarray" || typeName == "anycompatiblearray" +} + // argIndex reads a seed's "$n" pseudo-type as the zero-based index of the // argument whose type it stands for. func argIndex(typeName string) (int, bool) { diff --git a/internal/core/analyzer/scope.go b/internal/core/analyzer/scope.go index 020d3d1f12..218a8e0b11 100644 --- a/internal/core/analyzer/scope.go +++ b/internal/core/analyzer/scope.go @@ -100,19 +100,28 @@ func (a *analyzer) bindRangeFunction(rf *ast.RangeFunction) (scopeRel, error) { } // The arguments are typed against the scope built so far, which is what - // gives a placeholder passed to the function its type. - if _, err := a.typeFuncCall(call); err != nil { + // gives a placeholder passed to the function its type, and what + // resolves a polymorphic result: unnest of an array of strings yields + // strings. + t, err := a.typeFuncCall(call) + if err != nil { return scopeRel{}, err } - p := overloads[0] - rel := scopeRel{ - alias: name, - cols: []core.ClassColumn{{Name: name, TypeOID: p.ReturnTypeOID, NotNull: !p.ReturnNullable}}, - } + // The relation's alias names its single column too, as "unnest(tags) + // AS tag" does. + colName := name + rel := scopeRel{alias: name} if rf.Alias != nil && rf.Alias.Aliasname != nil && *rf.Alias.Aliasname != "" { rel.alias = *rf.Alias.Aliasname + colName = rel.alias + } + col := core.ClassColumn{Name: colName, TypeOID: t.typeOID, Type: t.expr, NotNull: !t.nullable} + if t.typeOID == 0 && t.expr == nil { + p := overloads[0] + col.TypeOID, col.NotNull = p.ReturnTypeOID, !p.ReturnNullable } + rel.cols = []core.ClassColumn{col} return rel, nil } diff --git a/internal/endtoend/testdata/analyze_arrays/googlesql/exec.json b/internal/endtoend/testdata/analyze_arrays/googlesql/exec.json new file mode 100644 index 0000000000..a53ddddc6d --- /dev/null +++ b/internal/endtoend/testdata/analyze_arrays/googlesql/exec.json @@ -0,0 +1,5 @@ +{ + "command": "analyze", + "args": ["--dialect", "googlesql", "--schema", "schema.sql", "query.sql"], + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/analyze_arrays/googlesql/query.sql b/internal/endtoend/testdata/analyze_arrays/googlesql/query.sql new file mode 100644 index 0000000000..5d4666d91f --- /dev/null +++ b/internal/endtoend/testdata/analyze_arrays/googlesql/query.sql @@ -0,0 +1,8 @@ +-- name: AlbumIDs :one +SELECT singer_id, ARRAY(SELECT album_id FROM albums WHERE albums.singer_id = singers.singer_id) AS album_ids +FROM singers WHERE singer_id = @singer_id; + +-- name: Literals :one +SELECT [1, 2, 3] AS ints, ARRAY['a'] AS strs, DATE '2024-01-02' AS d, + TIMESTAMP '2024-01-02 03:04:05+00' AS ts, NUMERIC '1.5' AS n, JSON '{"a": 1}' AS j, + b'abc' AS raw, INTERVAL 3 DAY AS iv; diff --git a/internal/endtoend/testdata/analyze_arrays/googlesql/schema.sql b/internal/endtoend/testdata/analyze_arrays/googlesql/schema.sql new file mode 100644 index 0000000000..667e087fb6 --- /dev/null +++ b/internal/endtoend/testdata/analyze_arrays/googlesql/schema.sql @@ -0,0 +1,13 @@ +CREATE TABLE singers ( + singer_id INT64 NOT NULL, + name STRING(MAX) NOT NULL, + tags ARRAY, + scores ARRAY NOT NULL, +) PRIMARY KEY (singer_id); + +CREATE TABLE albums ( + singer_id INT64 NOT NULL, + album_id INT64 NOT NULL, + title STRING(MAX), +) PRIMARY KEY (singer_id, album_id), + INTERLEAVE IN PARENT singers ON DELETE CASCADE; diff --git a/internal/endtoend/testdata/analyze_arrays/googlesql/stdout.json b/internal/endtoend/testdata/analyze_arrays/googlesql/stdout.json new file mode 100644 index 0000000000..920fc64da6 --- /dev/null +++ b/internal/endtoend/testdata/analyze_arrays/googlesql/stdout.json @@ -0,0 +1,109 @@ +[ + { + "name": "AlbumIDs", + "cmd": ":one", + "columns": [ + { + "name": "singer_id", + "type": { + "name": "int64" + }, + "table": "singers" + }, + { + "name": "album_ids", + "type": { + "name": "array", + "args": [ + { + "type": { + "name": "int64" + } + } + ] + } + } + ], + "params": [ + { + "number": 1, + "column": { + "name": "singer_id", + "type": { + "name": "int64" + }, + "table": "singers" + } + } + ] + }, + { + "name": "Literals", + "cmd": ":one", + "columns": [ + { + "name": "ints", + "type": { + "name": "array", + "args": [ + { + "type": { + "name": "int64" + } + } + ] + } + }, + { + "name": "strs", + "type": { + "name": "array", + "args": [ + { + "type": { + "name": "string" + } + } + ] + } + }, + { + "name": "d", + "type": { + "name": "date" + } + }, + { + "name": "ts", + "type": { + "name": "timestamp" + } + }, + { + "name": "n", + "type": { + "name": "numeric" + } + }, + { + "name": "j", + "type": { + "name": "json" + } + }, + { + "name": "raw", + "type": { + "name": "bytes" + } + }, + { + "name": "iv", + "type": { + "name": "interval" + } + } + ], + "params": [] + } +] diff --git a/internal/endtoend/testdata/analyze_types/bigquery/stdout.json b/internal/endtoend/testdata/analyze_types/bigquery/stdout.json index 8119ad12a2..34ebaf0b24 100644 --- a/internal/endtoend/testdata/analyze_types/bigquery/stdout.json +++ b/internal/endtoend/testdata/analyze_types/bigquery/stdout.json @@ -291,7 +291,17 @@ } }, { - "name": "f" + "name": "f", + "type": { + "name": "array", + "args": [ + { + "type": { + "name": "int64" + } + } + ] + } } ], "params": [ diff --git a/internal/endtoend/testdata/array_unnest/googlesql/exec.json b/internal/endtoend/testdata/array_unnest/googlesql/exec.json new file mode 100644 index 0000000000..2e996ca79d --- /dev/null +++ b/internal/endtoend/testdata/array_unnest/googlesql/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/array_unnest/googlesql/go/db.go b/internal/endtoend/testdata/array_unnest/googlesql/go/db.go new file mode 100644 index 0000000000..32faa9f2b8 --- /dev/null +++ b/internal/endtoend/testdata/array_unnest/googlesql/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +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/internal/endtoend/testdata/array_unnest/googlesql/go/models.go b/internal/endtoend/testdata/array_unnest/googlesql/go/models.go new file mode 100644 index 0000000000..62304209ba --- /dev/null +++ b/internal/endtoend/testdata/array_unnest/googlesql/go/models.go @@ -0,0 +1,24 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" + + "cloud.google.com/go/spanner" +) + +type Album struct { + SingerID int64 + AlbumID int64 + Title sql.NullString +} + +type Singer struct { + SingerID int64 + Name string + Tags []spanner.NullString + Scores []spanner.NullInt64 +} diff --git a/internal/endtoend/testdata/array_unnest/googlesql/go/query.sql.go b/internal/endtoend/testdata/array_unnest/googlesql/go/query.sql.go new file mode 100644 index 0000000000..3443ac6d1a --- /dev/null +++ b/internal/endtoend/testdata/array_unnest/googlesql/go/query.sql.go @@ -0,0 +1,144 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" + + "cloud.google.com/go/spanner" +) + +const albumTitles = `-- name: AlbumTitles :one +SELECT singer_id, ARRAY(SELECT title FROM albums WHERE albums.singer_id = singers.singer_id) AS titles +FROM singers WHERE singer_id = @singer_id +` + +type AlbumTitlesRow struct { + SingerID int64 + Titles []spanner.NullString +} + +func (q *Queries) AlbumTitles(ctx context.Context, singerID int64) (AlbumTitlesRow, error) { + row := q.db.QueryRowContext(ctx, albumTitles, sql.Named("singer_id", singerID)) + var i AlbumTitlesRow + err := row.Scan(&i.SingerID, &i.Titles) + return i, err +} + +const firstTag = `-- name: FirstTag :one +SELECT tags[OFFSET(0)] AS first_tag, tags[SAFE_ORDINAL(@n)] AS nth_tag, scores[SAFE_OFFSET(0)] AS first_score +FROM singers WHERE singer_id = @singer_id +` + +type FirstTagParams struct { + N int64 + SingerID int64 +} + +type FirstTagRow struct { + FirstTag sql.NullString + NthTag sql.NullString + FirstScore sql.NullInt64 +} + +func (q *Queries) FirstTag(ctx context.Context, arg FirstTagParams) (FirstTagRow, error) { + row := q.db.QueryRowContext(ctx, firstTag, sql.Named("n", arg.N), sql.Named("singer_id", arg.SingerID)) + var i FirstTagRow + err := row.Scan(&i.FirstTag, &i.NthTag, &i.FirstScore) + return i, err +} + +const singerTags = `-- name: SingerTags :many +SELECT singer_id, tag FROM singers, UNNEST(tags) AS tag +` + +type SingerTagsRow struct { + SingerID int64 + Tag sql.NullString +} + +func (q *Queries) SingerTags(ctx context.Context) ([]SingerTagsRow, error) { + rows, err := q.db.QueryContext(ctx, singerTags) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SingerTagsRow + for rows.Next() { + var i SingerTagsRow + if err := rows.Scan(&i.SingerID, &i.Tag); 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 singersByIDs = `-- name: SingersByIDs :many +SELECT singer_id, name FROM singers WHERE singer_id IN UNNEST(@ids) +` + +type SingersByIDsRow struct { + SingerID int64 + Name string +} + +func (q *Queries) SingersByIDs(ctx context.Context, ids []spanner.NullInt64) ([]SingersByIDsRow, error) { + rows, err := q.db.QueryContext(ctx, singersByIDs, sql.Named("ids", ids)) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SingersByIDsRow + for rows.Next() { + var i SingersByIDsRow + if err := rows.Scan(&i.SingerID, &i.Name); 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 singersNotIn = `-- name: SingersNotIn :many +SELECT singer_id FROM singers WHERE singer_id NOT IN UNNEST(@ids) +` + +func (q *Queries) SingersNotIn(ctx context.Context, ids []spanner.NullInt64) ([]int64, error) { + rows, err := q.db.QueryContext(ctx, singersNotIn, sql.Named("ids", ids)) + if err != nil { + return nil, err + } + defer rows.Close() + var items []int64 + for rows.Next() { + var singer_id int64 + if err := rows.Scan(&singer_id); err != nil { + return nil, err + } + items = append(items, singer_id) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/array_unnest/googlesql/query.sql b/internal/endtoend/testdata/array_unnest/googlesql/query.sql new file mode 100644 index 0000000000..caf34a04a2 --- /dev/null +++ b/internal/endtoend/testdata/array_unnest/googlesql/query.sql @@ -0,0 +1,16 @@ +-- name: SingersByIDs :many +SELECT singer_id, name FROM singers WHERE singer_id IN UNNEST(@ids); + +-- name: SingersNotIn :many +SELECT singer_id FROM singers WHERE singer_id NOT IN UNNEST(@ids); + +-- name: SingerTags :many +SELECT singer_id, tag FROM singers, UNNEST(tags) AS tag; + +-- name: AlbumTitles :one +SELECT singer_id, ARRAY(SELECT title FROM albums WHERE albums.singer_id = singers.singer_id) AS titles +FROM singers WHERE singer_id = @singer_id; + +-- name: FirstTag :one +SELECT tags[OFFSET(0)] AS first_tag, tags[SAFE_ORDINAL(@n)] AS nth_tag, scores[SAFE_OFFSET(0)] AS first_score +FROM singers WHERE singer_id = @singer_id; diff --git a/internal/endtoend/testdata/array_unnest/googlesql/schema.sql b/internal/endtoend/testdata/array_unnest/googlesql/schema.sql new file mode 100644 index 0000000000..667e087fb6 --- /dev/null +++ b/internal/endtoend/testdata/array_unnest/googlesql/schema.sql @@ -0,0 +1,13 @@ +CREATE TABLE singers ( + singer_id INT64 NOT NULL, + name STRING(MAX) NOT NULL, + tags ARRAY, + scores ARRAY NOT NULL, +) PRIMARY KEY (singer_id); + +CREATE TABLE albums ( + singer_id INT64 NOT NULL, + album_id INT64 NOT NULL, + title STRING(MAX), +) PRIMARY KEY (singer_id, album_id), + INTERLEAVE IN PARENT singers ON DELETE CASCADE; diff --git a/internal/endtoend/testdata/array_unnest/googlesql/sqlc.json b/internal/endtoend/testdata/array_unnest/googlesql/sqlc.json new file mode 100644 index 0000000000..40d6c76211 --- /dev/null +++ b/internal/endtoend/testdata/array_unnest/googlesql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "googlesql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/engine/googlesql/convert.go b/internal/engine/googlesql/convert.go index fc4f5f6327..4a4e19f139 100644 --- a/internal/engine/googlesql/convert.go +++ b/internal/engine/googlesql/convert.go @@ -479,10 +479,43 @@ func (c *cc) convertExpr(node zjast.Node) ast.Node { Arg: c.convertExpr(n.Array), Indirection: &ast.List{ Items: []ast.Node{ - &ast.A_Indices{Uidx: c.convertExpr(n.Position)}, + &ast.A_Indices{Uidx: c.convertExpr(arrayPosition(n.Position))}, }, }, } + case *zjast.ArrayConstructor: + return c.convertArrayConstructor(n) + case *zjast.DateOrTimeLiteral: + kind := strings.ToLower(strings.TrimPrefix(n.TypeKind, "TYPE_")) + return c.typedLiteral(n.Value, kind, n.Pos()) + case *zjast.NumericLiteral: + return c.typedLiteral(n.Value, "numeric", n.Pos()) + case *zjast.BigNumericLiteral: + return c.typedLiteral(n.Value, "bignumeric", n.Pos()) + case *zjast.JSONLiteral: + return c.typedLiteral(n.Value, "json", n.Pos()) + case *zjast.BytesLiteral: + var sb strings.Builder + for _, comp := range n.Components { + if comp != nil { + sb.WriteString(unquote(comp.Image)) + } + } + return &ast.TypeCast{ + Arg: &ast.A_Const{Val: &ast.String{Str: sb.String()}, Location: n.Pos()}, + TypeName: spelledTypeName("bytes"), + Location: n.Pos(), + } + case *zjast.IntervalExpr: + unit := "" + if n.DatePart != nil { + unit = strings.ToLower(n.DatePart.Name) + } + return &ast.IntervalExpr{ + Value: c.convertExpr(n.Value), + Unit: unit, + Location: n.Pos(), + } case *zjast.NamedArgument: return c.convertExpr(n.Value) case *zjast.ExpressionWithAlias: @@ -619,6 +652,25 @@ func (c *cc) convertBoolExpr(op ast.BoolExprType, operands []zjast.Node, loc int } func (c *cc) convertInExpression(n *zjast.InExpression) ast.Node { + // "x IN UNNEST(array)" tests membership of an array, which is what + // "x = ANY(array)" says. + if n.UnnestExpr != nil && len(n.UnnestExpr.Expressions) == 1 && n.UnnestExpr.Expressions[0] != nil { + var expr ast.Node = &ast.A_Expr{ + Kind: ast.A_Expr_Kind_OP_ANY, + Name: &ast.List{Items: []ast.Node{&ast.String{Str: "="}}}, + Lexpr: c.convertExpr(n.Lhs), + Rexpr: c.convertExpr(n.UnnestExpr.Expressions[0].Expr), + Location: n.Pos(), + } + if n.IsNot { + expr = &ast.BoolExpr{ + Boolop: ast.BoolExprTypeNot, + Args: &ast.List{Items: []ast.Node{expr}}, + Location: n.Pos(), + } + } + return expr + } in := &ast.In{ Expr: c.convertExpr(n.Lhs), Not: n.IsNot, @@ -696,6 +748,52 @@ func (c *cc) newFuncCall(parts []string, loc int) *ast.FuncCall { } } +// typedLiteral converts a literal spelled with its type, DATE '2024-01-01' +// or JSON '{}', to its string cast to that type. +func (c *cc) typedLiteral(value *zjast.StringLiteral, typ string, loc int) ast.Node { + if value == nil || typ == "" { + return todo(value) + } + return &ast.TypeCast{ + Arg: &ast.A_Const{Val: &ast.String{Str: stringLiteralValue(value)}, Location: value.Pos()}, + TypeName: spelledTypeName(typ), + Location: loc, + } +} + +// convertArrayConstructor converts [a, b] and ARRAY[a, b]. An explicit +// element type is a cast of the array to it, which is what gives an empty +// array its type. +func (c *cc) convertArrayConstructor(n *zjast.ArrayConstructor) ast.Node { + arr := &ast.A_ArrayExpr{Elements: &ast.List{}, Location: n.Pos()} + for _, elem := range n.Elements { + arr.Elements.Items = append(arr.Elements.Items, c.convertExpr(elem)) + } + if n.Type == nil { + return arr + } + return &ast.TypeCast{ + Arg: arr, + TypeName: spelledTypeName(typeName(n.Type)), + Location: n.Pos(), + } +} + +// arrayPosition unwraps the OFFSET, ORDINAL, SAFE_OFFSET or SAFE_ORDINAL +// that says how an array subscript counts to the position it wraps. They all +// address one element, so they type alike. +func arrayPosition(node zjast.Node) zjast.Node { + call, ok := node.(*zjast.FunctionCall) + if !ok || call.IsChained || len(call.Args) != 1 { + return node + } + switch strings.ToUpper(strings.Join(pathParts(call.Function), ".")) { + case "OFFSET", "ORDINAL", "SAFE_OFFSET", "SAFE_ORDINAL": + return call.Args[0] + } + return node +} + func (c *cc) convertFunctionCall(n *zjast.FunctionCall) ast.Node { // Chained calls ("expr.method(...)") have no sqlc AST equivalent. if n.IsChained { diff --git a/internal/engine/googlesql/dialect/functions.jsonl b/internal/engine/googlesql/dialect/functions.jsonl index 42bf28990e..eec5451ebe 100644 --- a/internal/engine/googlesql/dialect/functions.jsonl +++ b/internal/engine/googlesql/dialect/functions.jsonl @@ -1 +1,2 @@ {"name": "count", "kind": "a", "returns": "int64"} +{"name": "unnest", "args": [{"type": "anyarray"}], "returns": "anyelement", "nullable": true} From ddc27a39838df7a62e00c2215c59595747cbe1b5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 18:20:36 +0000 Subject: [PATCH 3/4] googlesql: seed Spanner's functions and operators functions.jsonl held only count, so every other function typed as any. It now lists 291 signatures across aggregates, math, strings, dates and timestamps, JSON, arrays, hashing, search and vector distance. Their return types, and those of the operators, were read from Spanner Omni by compiling each call in PLAN mode; the search functions and PENDING_COMMIT_TIMESTAMP, which cannot be compiled in a select list, are typed from Spanner's reference. Spanner has no window functions, so none are listed. GoogleSQL now propagates NULL through function arguments, with the functions that return NULL from non-null input marked nullable. The converter maps COALESCE, IFNULL, NULLIF, IF, GREATEST and LEAST onto the nodes the core types them by, makes SAFE.f(...) nullable, passes date parts (DAY, WEEK(MONDAY)) as strings rather than columns, and converts EXTRACT. In the core, an operator a dialect lists now replaces the one its arithmetic rule generates for the same operands, which is how INT64 / INT64 is a FLOAT64; and a placeholder passed for a polymorphic parameter takes its type from the other arguments, so the value in array_includes(array, @v) is the array's element. analyze_functions is checked against Spanner Omni; func_nullability covers what Spanner's wire types cannot report. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8QoGi6VRtADkb4jL3BFHP --- internal/core/analyzer/expr.go | 39 ++- internal/core/seed/seed.go | 25 +- .../analyze_functions/googlesql/exec.json | 5 + .../analyze_functions/googlesql/query.sql | 25 ++ .../analyze_functions/googlesql/schema.sql | 12 + .../analyze_functions/googlesql/stdout.json | 250 +++++++++++++++ .../func_nullability/googlesql/exec.json | 3 + .../func_nullability/googlesql/go/db.go | 31 ++ .../func_nullability/googlesql/go/models.go | 24 ++ .../googlesql/go/query.sql.go | 205 ++++++++++++ .../func_nullability/googlesql/query.sql | 23 ++ .../func_nullability/googlesql/schema.sql | 10 + .../func_nullability/googlesql/sqlc.json | 12 + internal/engine/googlesql/convert.go | 116 ++++++- .../engine/googlesql/dialect/dialect.json | 3 +- .../engine/googlesql/dialect/functions.jsonl | 291 +++++++++++++++++- .../engine/googlesql/dialect/operators.jsonl | 82 +++++ 17 files changed, 1149 insertions(+), 7 deletions(-) create mode 100644 internal/endtoend/testdata/analyze_functions/googlesql/exec.json create mode 100644 internal/endtoend/testdata/analyze_functions/googlesql/query.sql create mode 100644 internal/endtoend/testdata/analyze_functions/googlesql/schema.sql create mode 100644 internal/endtoend/testdata/analyze_functions/googlesql/stdout.json create mode 100644 internal/endtoend/testdata/func_nullability/googlesql/exec.json create mode 100644 internal/endtoend/testdata/func_nullability/googlesql/go/db.go create mode 100644 internal/endtoend/testdata/func_nullability/googlesql/go/models.go create mode 100644 internal/endtoend/testdata/func_nullability/googlesql/go/query.sql.go create mode 100644 internal/endtoend/testdata/func_nullability/googlesql/query.sql create mode 100644 internal/endtoend/testdata/func_nullability/googlesql/schema.sql create mode 100644 internal/endtoend/testdata/func_nullability/googlesql/sqlc.json diff --git a/internal/core/analyzer/expr.go b/internal/core/analyzer/expr.go index 8b7b716a75..62a69b273d 100644 --- a/internal/core/analyzer/expr.go +++ b/internal/core/analyzer/expr.go @@ -896,12 +896,20 @@ func (a *analyzer) typeFuncCall(f *ast.FuncCall) (exprType, error) { } p := a.pickOverload(overloads, argOIDs) // An argument that is a bare placeholder takes the parameter's type, - // unless the parameter is polymorphic and says nothing. + // unless the parameter is polymorphic, when it takes what the other + // arguments say: the element of the array passed for an anyarray, or + // an array of the value passed for an anyelement, as + // array_includes(anyarray, anyelement) relates them. for i, arg := range args { if i >= len(p.ArgTypes) { break } if a.isPolymorphicOID(p.ArgTypes[i]) { + if t, ok := a.polymorphicArg(p, i, argTypes); ok { + if err := a.typeOperands(arg, t); err != nil { + return exprType{}, err + } + } continue } if err := a.typeOperands(arg, exprType{typeOID: p.ArgTypes[i]}); err != nil { @@ -1002,6 +1010,35 @@ func (a *analyzer) returnType(p core.ProcOverload, argTypes []exprType) exprType return exprType{typeOID: p.ReturnTypeOID} } +// polymorphicArg is the type the other arguments of a call give its i-th, +// polymorphic, parameter: the element of an array passed for an anyarray +// when the parameter is an element, or an array of an element passed for +// an anyelement when the parameter is an array. +func (a *analyzer) polymorphicArg(p core.ProcOverload, i int, argTypes []exprType) (exprType, bool) { + name, err := a.cat.TypeName(p.ArgTypes[i]) + if err != nil || !isPolymorphic(name) { + return exprType{}, false + } + for j, other := range p.ArgTypes { + if j == i || j >= len(argTypes) || (argTypes[j].typeOID == 0 && argTypes[j].expr == nil) { + continue + } + otherName, err := a.cat.TypeName(other) + if err != nil || !isPolymorphic(otherName) { + continue + } + switch { + case isPolymorphicArray(otherName) && !isPolymorphicArray(name): + if element := a.exprOf(argTypes[j]).Element(); element != nil { + return a.lookupType(element), true + } + case !isPolymorphicArray(otherName) && isPolymorphicArray(name): + return a.arrayOf(argTypes[j]), true + } + } + return exprType{}, false +} + // isPolymorphicArray reports whether a polymorphic type stands for an array. func isPolymorphicArray(typeName string) bool { return typeName == "anyarray" || typeName == "anycompatiblearray" diff --git a/internal/core/seed/seed.go b/internal/core/seed/seed.go index e14f17aac8..064afedae8 100644 --- a/internal/core/seed/seed.go +++ b/internal/core/seed/seed.go @@ -320,12 +320,24 @@ func apply(cat *core.Catalog, fsys fs.FS, settings Settings) error { if err := b.rules(); err != nil { return err } - if err := b.categoryOperators(); err != nil { + // An operator the dialect lists replaces the one its rules would + // generate for the same operands, as GoogleSQL's INT64 / INT64 is a + // FLOAT64, so the list is read before the rules apply. + var operators []Operator + if err := stream(fsys, OperatorsFile, func(op Operator) error { + operators = append(operators, op) + return nil + }); err != nil { return err } - if err := stream(fsys, OperatorsFile, b.addOperator); err != nil { + if err := b.categoryOperators(operators); err != nil { return err } + for i, op := range operators { + if err := b.addOperator(op); err != nil { + return fmt.Errorf("seed: %s: record %d: %w", OperatorsFile, i+1, err) + } + } if err := stream(fsys, CastsFile, b.addCast); err != nil { return err } @@ -713,8 +725,12 @@ func (b *builder) rules() error { return nil } -func (b *builder) categoryOperators() error { +func (b *builder) categoryOperators(listed []Operator) error { s := b.settings + overridden := map[[3]string]bool{} + for _, op := range listed { + overridden[[3]string{op.Name, strings.ToLower(op.Left), strings.ToLower(op.Right)}] = true + } boolOID, ok := b.oids[strings.ToLower(s.Bool)] if len(s.Comparison) > 0 && !ok { return fmt.Errorf("seed %s: comparison operators need a bool type", s.Dialect) @@ -729,6 +745,9 @@ func (b *builder) categoryOperators() error { } if strings.Contains(s.ArithmeticCategories, t.category) { for _, op := range s.Arithmetic { + if overridden[[3]string{op, strings.ToLower(t.name), strings.ToLower(t.name)}] { + continue + } if err := b.operator(op, t.name, t.name, t.name, b.oids[t.name]); err != nil { return err } diff --git a/internal/endtoend/testdata/analyze_functions/googlesql/exec.json b/internal/endtoend/testdata/analyze_functions/googlesql/exec.json new file mode 100644 index 0000000000..a53ddddc6d --- /dev/null +++ b/internal/endtoend/testdata/analyze_functions/googlesql/exec.json @@ -0,0 +1,5 @@ +{ + "command": "analyze", + "args": ["--dialect", "googlesql", "--schema", "schema.sql", "query.sql"], + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/analyze_functions/googlesql/query.sql b/internal/endtoend/testdata/analyze_functions/googlesql/query.sql new file mode 100644 index 0000000000..d441415585 --- /dev/null +++ b/internal/endtoend/testdata/analyze_functions/googlesql/query.sql @@ -0,0 +1,25 @@ +-- name: Strings :many +SELECT LENGTH(name) AS len, UPPER(name) AS upper_name, CONCAT(name, '!') AS shout, + SPLIT(name, ',') AS parts, STARTS_WITH(name, 'a') AS starts, TO_HEX(SHA256(name)) AS digest, + FARM_FINGERPRINT(name) AS fingerprint, REPLACE(name, 'a', 'b') AS replaced, TO_BASE64(payload) AS b64 +FROM events; + +-- name: Numbers :many +SELECT ABS(id) AS abs_id, id / 2 AS half, amount * id AS scaled, ratio + id AS shifted, + ROUND(ratio, 2) AS rounded, MOD(id, 7) AS bucket, DIV(id, 2) AS halved, COSINE_DISTANCE(embedding, embedding) AS distance +FROM events; + +-- name: Times :many +SELECT DATE_ADD(happened_on, INTERVAL 1 DAY) AS next_day, DATE_DIFF(happened_on, happened_on, DAY) AS days, + TIMESTAMP_TRUNC(happened_at, HOUR) AS hour, EXTRACT(YEAR FROM happened_at) AS year, + EXTRACT(DATE FROM happened_at) AS day, UNIX_SECONDS(happened_at) AS epoch, + FORMAT_TIMESTAMP('%Y', happened_at) AS formatted +FROM events; + +-- name: Collections :many +SELECT ARRAY_LENGTH(scores) AS n, ARRAY_REVERSE(scores) AS reversed, GENERATE_ARRAY(1, id) AS series, + TO_JSON(id) AS as_json, JSON_TYPE(details) AS json_kind +FROM events; + +-- name: Counts :one +SELECT COUNT(*) AS total, COUNT(name) AS named, COUNTIF(ratio > 0.5) AS high FROM events; diff --git a/internal/endtoend/testdata/analyze_functions/googlesql/schema.sql b/internal/endtoend/testdata/analyze_functions/googlesql/schema.sql new file mode 100644 index 0000000000..ea03d7fcef --- /dev/null +++ b/internal/endtoend/testdata/analyze_functions/googlesql/schema.sql @@ -0,0 +1,12 @@ +CREATE TABLE events ( + id INT64 NOT NULL, + name STRING(MAX) NOT NULL, + payload BYTES(MAX) NOT NULL, + amount NUMERIC NOT NULL, + ratio FLOAT64 NOT NULL, + happened_on DATE NOT NULL, + happened_at TIMESTAMP NOT NULL, + details JSON NOT NULL, + scores ARRAY NOT NULL, + embedding ARRAY NOT NULL, +) PRIMARY KEY (id); diff --git a/internal/endtoend/testdata/analyze_functions/googlesql/stdout.json b/internal/endtoend/testdata/analyze_functions/googlesql/stdout.json new file mode 100644 index 0000000000..5e44b966d9 --- /dev/null +++ b/internal/endtoend/testdata/analyze_functions/googlesql/stdout.json @@ -0,0 +1,250 @@ +[ + { + "name": "Strings", + "cmd": ":many", + "columns": [ + { + "name": "len", + "type": { + "name": "int64" + } + }, + { + "name": "upper_name", + "type": { + "name": "string" + } + }, + { + "name": "shout", + "type": { + "name": "string" + } + }, + { + "name": "parts", + "type": { + "name": "array", + "args": [ + { + "type": { + "name": "string" + } + } + ] + } + }, + { + "name": "starts", + "type": { + "name": "bool" + } + }, + { + "name": "digest", + "type": { + "name": "string" + } + }, + { + "name": "fingerprint", + "type": { + "name": "int64" + } + }, + { + "name": "replaced", + "type": { + "name": "string" + } + }, + { + "name": "b64", + "type": { + "name": "string" + } + } + ], + "params": [] + }, + { + "name": "Numbers", + "cmd": ":many", + "columns": [ + { + "name": "abs_id", + "type": { + "name": "int64" + } + }, + { + "name": "half", + "type": { + "name": "float64" + } + }, + { + "name": "scaled", + "type": { + "name": "numeric" + } + }, + { + "name": "shifted", + "type": { + "name": "float64" + } + }, + { + "name": "rounded", + "type": { + "name": "float64" + } + }, + { + "name": "bucket", + "type": { + "name": "int64" + } + }, + { + "name": "halved", + "type": { + "name": "int64" + } + }, + { + "name": "distance", + "type": { + "name": "float64" + } + } + ], + "params": [] + }, + { + "name": "Times", + "cmd": ":many", + "columns": [ + { + "name": "next_day", + "type": { + "name": "date" + } + }, + { + "name": "days", + "type": { + "name": "int64" + } + }, + { + "name": "hour", + "type": { + "name": "timestamp" + } + }, + { + "name": "year", + "type": { + "name": "int64" + } + }, + { + "name": "day", + "type": { + "name": "date" + } + }, + { + "name": "epoch", + "type": { + "name": "int64" + } + }, + { + "name": "formatted", + "type": { + "name": "string" + } + } + ], + "params": [] + }, + { + "name": "Collections", + "cmd": ":many", + "columns": [ + { + "name": "n", + "type": { + "name": "int64" + } + }, + { + "name": "reversed", + "type": { + "name": "array", + "args": [ + { + "type": { + "name": "int64" + } + } + ] + } + }, + { + "name": "series", + "type": { + "name": "array", + "args": [ + { + "type": { + "name": "int64" + } + } + ] + } + }, + { + "name": "as_json", + "type": { + "name": "json" + } + }, + { + "name": "json_kind", + "type": { + "name": "string" + } + } + ], + "params": [] + }, + { + "name": "Counts", + "cmd": ":one", + "columns": [ + { + "name": "total", + "type": { + "name": "int64" + } + }, + { + "name": "named", + "type": { + "name": "int64" + } + }, + { + "name": "high", + "type": { + "name": "int64" + } + } + ], + "params": [] + } +] diff --git a/internal/endtoend/testdata/func_nullability/googlesql/exec.json b/internal/endtoend/testdata/func_nullability/googlesql/exec.json new file mode 100644 index 0000000000..2e996ca79d --- /dev/null +++ b/internal/endtoend/testdata/func_nullability/googlesql/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/func_nullability/googlesql/go/db.go b/internal/endtoend/testdata/func_nullability/googlesql/go/db.go new file mode 100644 index 0000000000..32faa9f2b8 --- /dev/null +++ b/internal/endtoend/testdata/func_nullability/googlesql/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *sql.Row +} + +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/internal/endtoend/testdata/func_nullability/googlesql/go/models.go b/internal/endtoend/testdata/func_nullability/googlesql/go/models.go new file mode 100644 index 0000000000..2880a343dc --- /dev/null +++ b/internal/endtoend/testdata/func_nullability/googlesql/go/models.go @@ -0,0 +1,24 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" + "math/big" + "time" + + "cloud.google.com/go/spanner" +) + +type Event struct { + ID int64 + Name string + Nickname sql.NullString + Amount big.Rat + Ratio sql.NullFloat64 + HappenedAt time.Time + Details spanner.NullJSON + Scores []spanner.NullInt64 +} diff --git a/internal/endtoend/testdata/func_nullability/googlesql/go/query.sql.go b/internal/endtoend/testdata/func_nullability/googlesql/go/query.sql.go new file mode 100644 index 0000000000..62cd454cf2 --- /dev/null +++ b/internal/endtoend/testdata/func_nullability/googlesql/go/query.sql.go @@ -0,0 +1,205 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" + "time" + + "cloud.google.com/go/civil" + "cloud.google.com/go/spanner" +) + +const aggregates = `-- name: Aggregates :one +SELECT COUNT(*) AS total, SUM(amount) AS total_amount, AVG(id) AS mean_id, MAX(name) AS last_name, + ARRAY_AGG(id) AS ids, STRING_AGG(name, ',') AS names +FROM events +` + +type AggregatesRow struct { + Total int64 + TotalAmount spanner.NullNumeric + MeanID sql.NullFloat64 + LastName sql.NullString + Ids []spanner.NullInt64 + Names sql.NullString +} + +func (q *Queries) Aggregates(ctx context.Context) (AggregatesRow, error) { + row := q.db.QueryRowContext(ctx, aggregates) + var i AggregatesRow + err := row.Scan( + &i.Total, + &i.TotalAmount, + &i.MeanID, + &i.LastName, + &i.Ids, + &i.Names, + ) + return i, err +} + +const conditionals = `-- name: Conditionals :many +SELECT COALESCE(nickname, name) AS display_name, IFNULL(ratio, 0) AS ratio_or_zero, + NULLIF(name, '') AS non_empty, IF(id > 10, name, nickname) AS chosen, GREATEST(id, 1) AS at_least_one +FROM events +` + +type ConditionalsRow struct { + DisplayName string + RatioOrZero float64 + NonEmpty sql.NullString + Chosen sql.NullString + AtLeastOne int64 +} + +func (q *Queries) Conditionals(ctx context.Context) ([]ConditionalsRow, error) { + rows, err := q.db.QueryContext(ctx, conditionals) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ConditionalsRow + for rows.Next() { + var i ConditionalsRow + if err := rows.Scan( + &i.DisplayName, + &i.RatioOrZero, + &i.NonEmpty, + &i.Chosen, + &i.AtLeastOne, + ); 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 mayBeNull = `-- name: MayBeNull :many +SELECT JSON_VALUE(details, '$.kind') AS kind, REGEXP_EXTRACT(name, 'a+') AS run, + SAFE_DIVIDE(id, 0) AS ratio, SAFE.SUBSTR(name, 1, 3) AS prefix, ARRAY_FIRST(scores) AS first_score +FROM events +` + +type MayBeNullRow struct { + Kind sql.NullString + Run sql.NullString + Ratio sql.NullFloat64 + Prefix sql.NullString + FirstScore sql.NullInt64 +} + +func (q *Queries) MayBeNull(ctx context.Context) ([]MayBeNullRow, error) { + rows, err := q.db.QueryContext(ctx, mayBeNull) + if err != nil { + return nil, err + } + defer rows.Close() + var items []MayBeNullRow + for rows.Next() { + var i MayBeNullRow + if err := rows.Scan( + &i.Kind, + &i.Run, + &i.Ratio, + &i.Prefix, + &i.FirstScore, + ); 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 paramsFromFunctions = `-- name: ParamsFromFunctions :many +SELECT id FROM events +WHERE STARTS_WITH(name, @prefix) AND TIMESTAMP_TRUNC(happened_at, DAY) = @day + AND EXTRACT(DATE FROM happened_at) = @on AND ARRAY_INCLUDES(scores, @score) +` + +type ParamsFromFunctionsParams struct { + Prefix string + Day time.Time + On civil.Date + Score int64 +} + +func (q *Queries) ParamsFromFunctions(ctx context.Context, arg ParamsFromFunctionsParams) ([]int64, error) { + rows, err := q.db.QueryContext(ctx, paramsFromFunctions, + sql.Named("prefix", arg.Prefix), + sql.Named("day", arg.Day), + sql.Named("on", arg.On), + sql.Named("score", arg.Score), + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []int64 + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + items = append(items, id) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const propagates = `-- name: Propagates :many +SELECT UPPER(name) AS upper_name, UPPER(nickname) AS upper_nickname, LENGTH(nickname) AS nickname_length +FROM events +` + +type PropagatesRow struct { + UpperName string + UpperNickname sql.NullString + NicknameLength sql.NullInt64 +} + +func (q *Queries) Propagates(ctx context.Context) ([]PropagatesRow, error) { + rows, err := q.db.QueryContext(ctx, propagates) + if err != nil { + return nil, err + } + defer rows.Close() + var items []PropagatesRow + for rows.Next() { + var i PropagatesRow + if err := rows.Scan(&i.UpperName, &i.UpperNickname, &i.NicknameLength); 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 +} diff --git a/internal/endtoend/testdata/func_nullability/googlesql/query.sql b/internal/endtoend/testdata/func_nullability/googlesql/query.sql new file mode 100644 index 0000000000..517af79cc8 --- /dev/null +++ b/internal/endtoend/testdata/func_nullability/googlesql/query.sql @@ -0,0 +1,23 @@ +-- name: Aggregates :one +SELECT COUNT(*) AS total, SUM(amount) AS total_amount, AVG(id) AS mean_id, MAX(name) AS last_name, + ARRAY_AGG(id) AS ids, STRING_AGG(name, ',') AS names +FROM events; + +-- name: Propagates :many +SELECT UPPER(name) AS upper_name, UPPER(nickname) AS upper_nickname, LENGTH(nickname) AS nickname_length +FROM events; + +-- name: MayBeNull :many +SELECT JSON_VALUE(details, '$.kind') AS kind, REGEXP_EXTRACT(name, 'a+') AS run, + SAFE_DIVIDE(id, 0) AS ratio, SAFE.SUBSTR(name, 1, 3) AS prefix, ARRAY_FIRST(scores) AS first_score +FROM events; + +-- name: Conditionals :many +SELECT COALESCE(nickname, name) AS display_name, IFNULL(ratio, 0) AS ratio_or_zero, + NULLIF(name, '') AS non_empty, IF(id > 10, name, nickname) AS chosen, GREATEST(id, 1) AS at_least_one +FROM events; + +-- name: ParamsFromFunctions :many +SELECT id FROM events +WHERE STARTS_WITH(name, @prefix) AND TIMESTAMP_TRUNC(happened_at, DAY) = @day + AND EXTRACT(DATE FROM happened_at) = @on AND ARRAY_INCLUDES(scores, @score); diff --git a/internal/endtoend/testdata/func_nullability/googlesql/schema.sql b/internal/endtoend/testdata/func_nullability/googlesql/schema.sql new file mode 100644 index 0000000000..cce2a7cf8e --- /dev/null +++ b/internal/endtoend/testdata/func_nullability/googlesql/schema.sql @@ -0,0 +1,10 @@ +CREATE TABLE events ( + id INT64 NOT NULL, + name STRING(MAX) NOT NULL, + nickname STRING(MAX), + amount NUMERIC NOT NULL, + ratio FLOAT64, + happened_at TIMESTAMP NOT NULL, + details JSON NOT NULL, + scores ARRAY NOT NULL, +) PRIMARY KEY (id); diff --git a/internal/endtoend/testdata/func_nullability/googlesql/sqlc.json b/internal/endtoend/testdata/func_nullability/googlesql/sqlc.json new file mode 100644 index 0000000000..40d6c76211 --- /dev/null +++ b/internal/endtoend/testdata/func_nullability/googlesql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "googlesql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/engine/googlesql/convert.go b/internal/engine/googlesql/convert.go index 4a4e19f139..aa694299b6 100644 --- a/internal/engine/googlesql/convert.go +++ b/internal/engine/googlesql/convert.go @@ -485,6 +485,8 @@ func (c *cc) convertExpr(node zjast.Node) ast.Node { } case *zjast.ArrayConstructor: return c.convertArrayConstructor(n) + case *zjast.ExtractExpression: + return c.convertExtractExpression(n) case *zjast.DateOrTimeLiteral: kind := strings.ToLower(strings.TrimPrefix(n.TypeKind, "TYPE_")) return c.typedLiteral(n.Value, kind, n.Pos()) @@ -748,6 +750,43 @@ func (c *cc) newFuncCall(parts []string, loc int) *ast.FuncCall { } } +// convertConditional converts the conditional functions whose result is one +// of their arguments to the nodes that say so, which is what types the +// result after the arguments and makes it nullable only when it can be: +// COALESCE and IFNULL, NULLIF, IF, GREATEST and LEAST. It returns nil for +// anything else. +func (c *cc) convertConditional(name string, args []zjast.Node, loc int) ast.Node { + convert := func() *ast.List { + list := &ast.List{} + for _, arg := range args { + list.Items = append(list.Items, c.convertExpr(arg)) + } + return list + } + switch { + case (name == "coalesce" && len(args) > 0) || (name == "ifnull" && len(args) == 2): + return &ast.CoalesceExpr{Args: convert(), Location: loc} + case name == "nullif" && len(args) == 2: + return &ast.A_Expr{ + Kind: ast.A_Expr_Kind_NULLIF, + Name: &ast.List{Items: []ast.Node{&ast.String{Str: "="}}}, + Lexpr: c.convertExpr(args[0]), + Rexpr: c.convertExpr(args[1]), + Location: loc, + } + case name == "if" && len(args) == 3: + return c.buildCaseExpr(nil, args, loc) + case (name == "greatest" || name == "least") && len(args) > 0: + // The ops number as PostgreSQL's do: IS_GREATEST, then IS_LEAST. + op := ast.MinMaxOp(0) + if name == "least" { + op = 1 + } + return &ast.MinMaxExpr{Op: op, Args: convert(), Location: loc} + } + return nil +} + // typedLiteral converts a literal spelled with its type, DATE '2024-01-01' // or JSON '{}', to its string cast to that type. func (c *cc) typedLiteral(value *zjast.StringLiteral, typ string, loc int) ast.Node { @@ -803,13 +842,38 @@ func (c *cc) convertFunctionCall(n *zjast.FunctionCall) ast.Node { if len(parts) == 0 { return todo(n) } + // SAFE.f(...) is f(...), except that it returns NULL where f raises an + // error, so its result is f's made nullable: a CASE with no ELSE. + if len(parts) == 2 && strings.EqualFold(parts[0], "SAFE") { + call := *n + call.Function = &zjast.PathExpression{Names: n.Function.Names[1:]} + return &ast.CaseExpr{ + Args: &ast.List{Items: []ast.Node{&ast.CaseWhen{ + Expr: &ast.A_Const{Val: &ast.Boolean{Boolval: true}, Location: n.Pos()}, + Result: c.convertFunctionCall(&call), + }}}, + Location: n.Pos(), + } + } + if len(parts) == 1 && !n.Distinct && n.OrderBy == nil { + if expr := c.convertConditional(strings.ToLower(parts[0]), n.Args, n.Pos()); expr != nil { + return expr + } + } fc := c.newFuncCall(parts, n.Pos()) fc.AggDistinct = n.Distinct - for _, arg := range n.Args { + partArg, hasPart := datePartArg[fc.Func.Name] + for i, arg := range n.Args { if _, ok := arg.(*zjast.Star); ok { fc.AggStar = true continue } + if hasPart && i == partArg { + if part, ok := datePart(arg); ok { + fc.Args.Items = append(fc.Args.Items, &ast.A_Const{Val: &ast.String{Str: part}, Location: arg.Pos()}) + continue + } + } fc.Args.Items = append(fc.Args.Items, c.convertExpr(arg)) } if n.OrderBy != nil { @@ -818,6 +882,56 @@ func (c *cc) convertFunctionCall(n *zjast.FunctionCall) ast.Node { return fc } +// datePartArg is the position of the argument naming a date part, DAY or +// WEEK(MONDAY), in the functions that take one. The part is a keyword, not +// a column, so it is converted to a string. +var datePartArg = map[string]int{ + "date_diff": 2, + "date_trunc": 1, + "timestamp_diff": 2, + "timestamp_trunc": 1, + "last_day": 1, +} + +// datePart spells a date part argument: DAY, or WEEK(MONDAY). +func datePart(node zjast.Node) (string, bool) { + switch n := node.(type) { + case *zjast.PathExpression: + if parts := pathParts(n); len(parts) == 1 { + return strings.ToUpper(parts[0]), true + } + case *zjast.FunctionCall: + parts := pathParts(n.Function) + if len(parts) == 1 && len(n.Args) == 1 { + if arg, ok := datePart(n.Args[0]); ok { + return strings.ToUpper(parts[0]) + "(" + arg + ")", true + } + } + } + return "", false +} + +// convertExtractExpression converts EXTRACT(part FROM expr). Every part is +// an INT64 but DATE, which is the date of a timestamp, so EXTRACT is the +// extract function the dialect seeds as INT64, and EXTRACT(DATE FROM ts) +// is date(ts). +func (c *cc) convertExtractExpression(n *zjast.ExtractExpression) ast.Node { + part, ok := datePart(n.LhsExpr) + if !ok || n.RhsExpr == nil { + return todo(n) + } + name := "extract" + if part == "DATE" { + name = "date" + } + fc := c.newFuncCall([]string{name}, n.Pos()) + fc.Args.Items = append(fc.Args.Items, c.convertExpr(n.RhsExpr)) + if n.TimeZone != nil { + fc.Args.Items = append(fc.Args.Items, c.convertExpr(n.TimeZone)) + } + return fc +} + func (c *cc) convertAnalyticFunctionCall(n *zjast.AnalyticFunctionCall) ast.Node { base := c.convertExpr(n.Expr) fc, ok := base.(*ast.FuncCall) diff --git a/internal/engine/googlesql/dialect/dialect.json b/internal/engine/googlesql/dialect/dialect.json index 728546d80b..ef0e61426a 100644 --- a/internal/engine/googlesql/dialect/dialect.json +++ b/internal/engine/googlesql/dialect/dialect.json @@ -11,5 +11,6 @@ "comparison_categories": "BNSDTU", "arithmetic": ["+", "-", "*", "/"], "arithmetic_categories": "N", - "idents": ["max"] + "idents": ["max"], + "propagate_nullable": true } diff --git a/internal/engine/googlesql/dialect/functions.jsonl b/internal/engine/googlesql/dialect/functions.jsonl index eec5451ebe..6090fbcfde 100644 --- a/internal/engine/googlesql/dialect/functions.jsonl +++ b/internal/engine/googlesql/dialect/functions.jsonl @@ -1,2 +1,291 @@ -{"name": "count", "kind": "a", "returns": "int64"} +{"name": "abs", "args": [{"type": "int64"}], "returns": "int64"} +{"name": "abs", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "abs", "args": [{"type": "float32"}], "returns": "float32"} +{"name": "abs", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "acos", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "any_value", "kind": "a", "args": [{"type": "anyelement"}], "returns": "$1", "nullable": true} +{"name": "array_agg", "kind": "a", "args": [{"type": "anyelement"}], "returns": "array($1)", "nullable": true} +{"name": "array_concat", "args": [{"type": "anyarray"}, {"type": "anyarray"}], "returns": "$1"} +{"name": "array_concat_agg", "kind": "a", "args": [{"type": "anyarray"}], "returns": "$1", "nullable": true} +{"name": "array_first", "args": [{"type": "anyarray"}], "returns": "anyelement", "nullable": true} +{"name": "array_includes", "args": [{"type": "anyarray"}, {"type": "anyelement"}], "returns": "bool"} +{"name": "array_includes_all", "args": [{"type": "anyarray"}, {"type": "anyarray"}], "returns": "bool"} +{"name": "array_includes_any", "args": [{"type": "anyarray"}, {"type": "anyarray"}], "returns": "bool"} +{"name": "array_is_distinct", "args": [{"type": "anyarray"}], "returns": "bool"} +{"name": "array_last", "args": [{"type": "anyarray"}], "returns": "anyelement", "nullable": true} +{"name": "array_length", "args": [{"type": "anyarray"}], "returns": "int64"} +{"name": "array_max", "args": [{"type": "anyarray"}], "returns": "anyelement", "nullable": true} +{"name": "array_min", "args": [{"type": "anyarray"}], "returns": "anyelement", "nullable": true} +{"name": "array_reverse", "args": [{"type": "anyarray"}], "returns": "$1"} +{"name": "array_slice", "args": [{"type": "anyarray"}, {"type": "int64"}, {"type": "int64"}], "returns": "$1"} +{"name": "array_to_string", "args": [{"type": "array(string)"}, {"type": "string"}], "returns": "string"} +{"name": "array_to_string", "args": [{"type": "array(bytes)"}, {"type": "bytes"}], "returns": "bytes"} +{"name": "array_to_string", "args": [{"type": "array(string)"}, {"type": "string"}, {"type": "string"}], "returns": "string"} +{"name": "asin", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "atan", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "atan2", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64"} +{"name": "avg", "kind": "a", "args": [{"type": "int64"}], "returns": "float64", "nullable": true} +{"name": "avg", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} +{"name": "avg", "kind": "a", "args": [{"type": "float32"}], "returns": "float64", "nullable": true} +{"name": "avg", "kind": "a", "args": [{"type": "numeric"}], "returns": "numeric", "nullable": true} +{"name": "avg", "kind": "a", "args": [{"type": "interval"}], "returns": "interval", "nullable": true} +{"name": "bit_and", "kind": "a", "args": [{"type": "int64"}], "returns": "int64", "nullable": true} +{"name": "bit_count", "args": [{"type": "int64"}], "returns": "int64"} +{"name": "bit_count", "args": [{"type": "bytes"}], "returns": "int64"} +{"name": "bit_or", "kind": "a", "args": [{"type": "int64"}], "returns": "int64", "nullable": true} +{"name": "bit_reverse", "args": [{"type": "int64"}, {"type": "bool"}], "returns": "int64"} +{"name": "bit_xor", "kind": "a", "args": [{"type": "int64"}], "returns": "int64", "nullable": true} +{"name": "bool", "args": [{"type": "json"}], "returns": "bool"} +{"name": "byte_length", "args": [{"type": "string"}], "returns": "int64"} +{"name": "byte_length", "args": [{"type": "bytes"}], "returns": "int64"} +{"name": "ceil", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "ceil", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "ceiling", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "ceiling", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "char_length", "args": [{"type": "string"}], "returns": "int64"} +{"name": "character_length", "args": [{"type": "string"}], "returns": "int64"} +{"name": "code_points_to_bytes", "args": [{"type": "array(int64)"}], "returns": "bytes"} +{"name": "code_points_to_string", "args": [{"type": "array(int64)"}], "returns": "string"} +{"name": "concat", "args": [{"type": "string"}, {"type": "string"}], "returns": "string"} +{"name": "concat", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} +{"name": "cos", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "cosine_distance", "args": [{"type": "array(float64)"}, {"type": "array(float64)"}], "returns": "float64"} +{"name": "cosine_distance", "args": [{"type": "array(float32)"}, {"type": "array(float32)"}], "returns": "float64"} +{"name": "count", "kind": "a", "args": [{"type": "anyelement"}], "returns": "int64", "never_null": true} +{"name": "countif", "kind": "a", "args": [{"type": "bool"}], "returns": "int64", "never_null": true} +{"name": "current_date", "returns": "date"} +{"name": "current_timestamp", "returns": "timestamp"} +{"name": "date", "args": [{"type": "timestamp"}], "returns": "date"} +{"name": "date", "args": [{"type": "timestamp"}, {"type": "string"}], "returns": "date"} +{"name": "date", "args": [{"type": "int64"}, {"type": "int64"}, {"type": "int64"}], "returns": "date"} +{"name": "date_add", "args": [{"type": "date"}, {"type": "interval"}], "returns": "date"} +{"name": "date_diff", "args": [{"type": "date"}, {"type": "date"}, {"type": "string"}], "returns": "int64"} +{"name": "date_from_unix_date", "args": [{"type": "int64"}], "returns": "date"} +{"name": "date_sub", "args": [{"type": "date"}, {"type": "interval"}], "returns": "date"} +{"name": "date_trunc", "args": [{"type": "date"}, {"type": "string"}], "returns": "date"} +{"name": "div", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "int64"} +{"name": "div", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric"} +{"name": "dot_product", "args": [{"type": "array(float64)"}, {"type": "array(float64)"}], "returns": "float64"} +{"name": "dot_product", "args": [{"type": "array(float32)"}, {"type": "array(float32)"}], "returns": "float64"} +{"name": "dot_product", "args": [{"type": "array(int64)"}, {"type": "array(int64)"}], "returns": "float64"} +{"name": "ends_with", "args": [{"type": "string"}, {"type": "string"}], "returns": "bool"} +{"name": "ends_with", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bool"} +{"name": "euclidean_distance", "args": [{"type": "array(float64)"}, {"type": "array(float64)"}], "returns": "float64"} +{"name": "euclidean_distance", "args": [{"type": "array(float32)"}, {"type": "array(float32)"}], "returns": "float64"} +{"name": "exp", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "exp", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "extract", "args": [{"type": "timestamp"}], "returns": "int64"} +{"name": "extract", "args": [{"type": "date"}], "returns": "int64"} +{"name": "extract", "args": [{"type": "timestamp"}, {"type": "string"}], "returns": "int64"} +{"name": "farm_fingerprint", "args": [{"type": "string"}], "returns": "int64"} +{"name": "farm_fingerprint", "args": [{"type": "bytes"}], "returns": "int64"} +{"name": "float32", "args": [{"type": "json"}], "returns": "float32"} +{"name": "float64", "args": [{"type": "json"}], "returns": "float64"} +{"name": "floor", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "floor", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "format", "args": [{"type": "string"}], "returns": "string"} +{"name": "format_date", "args": [{"type": "string"}, {"type": "date"}], "returns": "string"} +{"name": "format_timestamp", "args": [{"type": "string"}, {"type": "timestamp"}], "returns": "string"} +{"name": "format_timestamp", "args": [{"type": "string"}, {"type": "timestamp"}, {"type": "string"}], "returns": "string"} +{"name": "from_base32", "args": [{"type": "string"}], "returns": "bytes"} +{"name": "from_base64", "args": [{"type": "string"}], "returns": "bytes"} +{"name": "from_hex", "args": [{"type": "string"}], "returns": "bytes"} +{"name": "generate_array", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "array(int64)"} +{"name": "generate_array", "args": [{"type": "int64"}, {"type": "int64"}, {"type": "int64"}], "returns": "array(int64)"} +{"name": "generate_array", "args": [{"type": "float64"}, {"type": "float64"}, {"type": "float64"}], "returns": "array(float64)"} +{"name": "generate_date_array", "args": [{"type": "date"}, {"type": "date"}], "returns": "array(date)"} +{"name": "generate_date_array", "args": [{"type": "date"}, {"type": "date"}, {"type": "interval"}], "returns": "array(date)"} +{"name": "generate_uuid", "returns": "string", "never_null": true} +{"name": "get_next_sequence_value", "args": [{"type": "anyelement"}], "returns": "int64", "never_null": true} +{"name": "ieee_divide", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64"} +{"name": "int64", "args": [{"type": "json"}], "returns": "int64"} +{"name": "is_inf", "args": [{"type": "float64"}], "returns": "bool"} +{"name": "is_nan", "args": [{"type": "float64"}], "returns": "bool"} +{"name": "json_array", "args": [{"type": "anyelement"}], "returns": "json"} +{"name": "json_array_append", "args": [{"type": "json"}, {"type": "string"}, {"type": "anyelement"}], "returns": "json"} +{"name": "json_array_insert", "args": [{"type": "json"}, {"type": "string"}, {"type": "anyelement"}], "returns": "json"} +{"name": "json_contains", "args": [{"type": "json"}, {"type": "json"}], "returns": "bool"} +{"name": "json_keys", "args": [{"type": "json"}], "returns": "array(string)"} +{"name": "json_object", "args": [{"type": "string"}, {"type": "anyelement"}], "returns": "json"} +{"name": "json_query", "args": [{"type": "json"}, {"type": "string"}], "returns": "json", "nullable": true} +{"name": "json_query", "args": [{"type": "string"}, {"type": "string"}], "returns": "string", "nullable": true} +{"name": "json_query_array", "args": [{"type": "json"}], "returns": "array(json)", "nullable": true} +{"name": "json_query_array", "args": [{"type": "json"}, {"type": "string"}], "returns": "array(json)", "nullable": true} +{"name": "json_remove", "args": [{"type": "json"}, {"type": "string"}], "returns": "json"} +{"name": "json_set", "args": [{"type": "json"}, {"type": "string"}, {"type": "anyelement"}], "returns": "json"} +{"name": "json_strip_nulls", "args": [{"type": "json"}], "returns": "json"} +{"name": "json_type", "args": [{"type": "json"}], "returns": "string"} +{"name": "json_value", "args": [{"type": "json"}, {"type": "string"}], "returns": "string", "nullable": true} +{"name": "json_value", "args": [{"type": "string"}, {"type": "string"}], "returns": "string", "nullable": true} +{"name": "json_value_array", "args": [{"type": "json"}], "returns": "array(string)", "nullable": true} +{"name": "json_value_array", "args": [{"type": "json"}, {"type": "string"}], "returns": "array(string)", "nullable": true} +{"name": "justify_days", "args": [{"type": "interval"}], "returns": "interval"} +{"name": "justify_hours", "args": [{"type": "interval"}], "returns": "interval"} +{"name": "justify_interval", "args": [{"type": "interval"}], "returns": "interval"} +{"name": "lax_bool", "args": [{"type": "json"}], "returns": "bool", "nullable": true} +{"name": "lax_float64", "args": [{"type": "json"}], "returns": "float64", "nullable": true} +{"name": "lax_int64", "args": [{"type": "json"}], "returns": "int64", "nullable": true} +{"name": "lax_string", "args": [{"type": "json"}], "returns": "string", "nullable": true} +{"name": "length", "args": [{"type": "string"}], "returns": "int64"} +{"name": "length", "args": [{"type": "bytes"}], "returns": "int64"} +{"name": "ln", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "ln", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "log", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "log", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "log", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64"} +{"name": "log10", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "log10", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "logical_and", "kind": "a", "args": [{"type": "bool"}], "returns": "bool", "nullable": true} +{"name": "logical_or", "kind": "a", "args": [{"type": "bool"}], "returns": "bool", "nullable": true} +{"name": "lower", "args": [{"type": "string"}], "returns": "string"} +{"name": "lower", "args": [{"type": "bytes"}], "returns": "bytes"} +{"name": "lpad", "args": [{"type": "string"}, {"type": "int64"}], "returns": "string"} +{"name": "lpad", "args": [{"type": "string"}, {"type": "int64"}, {"type": "string"}], "returns": "string"} +{"name": "lpad", "args": [{"type": "bytes"}, {"type": "int64"}, {"type": "bytes"}], "returns": "bytes"} +{"name": "ltrim", "args": [{"type": "string"}], "returns": "string"} +{"name": "ltrim", "args": [{"type": "string"}, {"type": "string"}], "returns": "string"} +{"name": "ltrim", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} +{"name": "make_interval", "args": [{"type": "int64"}, {"type": "int64"}, {"type": "int64"}], "returns": "interval"} +{"name": "max", "kind": "a", "args": [{"type": "anyelement"}], "returns": "$1", "nullable": true} +{"name": "md5", "args": [{"type": "string"}], "returns": "bytes"} +{"name": "md5", "args": [{"type": "bytes"}], "returns": "bytes"} +{"name": "min", "kind": "a", "args": [{"type": "anyelement"}], "returns": "$1", "nullable": true} +{"name": "mod", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "int64"} +{"name": "mod", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric"} +{"name": "new_uuid", "returns": "uuid", "never_null": true} +{"name": "normalize", "args": [{"type": "string"}], "returns": "string"} +{"name": "normalize_and_casefold", "args": [{"type": "string"}], "returns": "string"} +{"name": "parse_date", "args": [{"type": "string"}, {"type": "string"}], "returns": "date"} +{"name": "parse_json", "args": [{"type": "string"}], "returns": "json"} +{"name": "parse_timestamp", "args": [{"type": "string"}, {"type": "string"}], "returns": "timestamp"} +{"name": "parse_timestamp", "args": [{"type": "string"}, {"type": "string"}, {"type": "string"}], "returns": "timestamp"} +{"name": "pending_commit_timestamp", "returns": "timestamp", "never_null": true} +{"name": "pow", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64"} +{"name": "pow", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric"} +{"name": "power", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64"} +{"name": "power", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric"} +{"name": "regexp_contains", "args": [{"type": "string"}, {"type": "string"}], "returns": "bool"} +{"name": "regexp_contains", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bool"} +{"name": "regexp_extract", "args": [{"type": "string"}, {"type": "string"}], "returns": "string", "nullable": true} +{"name": "regexp_extract", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes", "nullable": true} +{"name": "regexp_extract_all", "args": [{"type": "string"}, {"type": "string"}], "returns": "array(string)"} +{"name": "regexp_extract_all", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "array(bytes)"} +{"name": "regexp_replace", "args": [{"type": "string"}, {"type": "string"}, {"type": "string"}], "returns": "string"} +{"name": "regexp_replace", "args": [{"type": "bytes"}, {"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} +{"name": "repeat", "args": [{"type": "string"}, {"type": "int64"}], "returns": "string"} +{"name": "repeat", "args": [{"type": "bytes"}, {"type": "int64"}], "returns": "bytes"} +{"name": "replace", "args": [{"type": "string"}, {"type": "string"}, {"type": "string"}], "returns": "string"} +{"name": "replace", "args": [{"type": "bytes"}, {"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} +{"name": "reverse", "args": [{"type": "string"}], "returns": "string"} +{"name": "reverse", "args": [{"type": "bytes"}], "returns": "bytes"} +{"name": "round", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "round", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "round", "args": [{"type": "float64"}, {"type": "int64"}], "returns": "float64"} +{"name": "round", "args": [{"type": "numeric"}, {"type": "int64"}], "returns": "numeric"} +{"name": "rpad", "args": [{"type": "string"}, {"type": "int64"}], "returns": "string"} +{"name": "rpad", "args": [{"type": "string"}, {"type": "int64"}, {"type": "string"}], "returns": "string"} +{"name": "rpad", "args": [{"type": "bytes"}, {"type": "int64"}, {"type": "bytes"}], "returns": "bytes"} +{"name": "rtrim", "args": [{"type": "string"}], "returns": "string"} +{"name": "rtrim", "args": [{"type": "string"}, {"type": "string"}], "returns": "string"} +{"name": "rtrim", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} +{"name": "safe_add", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "int64", "nullable": true} +{"name": "safe_add", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64", "nullable": true} +{"name": "safe_add", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric", "nullable": true} +{"name": "safe_convert_bytes_to_string", "args": [{"type": "bytes"}], "returns": "string"} +{"name": "safe_divide", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "float64", "nullable": true} +{"name": "safe_divide", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64", "nullable": true} +{"name": "safe_divide", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric", "nullable": true} +{"name": "safe_multiply", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "int64", "nullable": true} +{"name": "safe_multiply", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64", "nullable": true} +{"name": "safe_multiply", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric", "nullable": true} +{"name": "safe_negate", "args": [{"type": "int64"}], "returns": "int64", "nullable": true} +{"name": "safe_negate", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} +{"name": "safe_negate", "args": [{"type": "numeric"}], "returns": "numeric", "nullable": true} +{"name": "safe_subtract", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "int64", "nullable": true} +{"name": "safe_subtract", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64", "nullable": true} +{"name": "safe_subtract", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric", "nullable": true} +{"name": "safe_to_json", "args": [{"type": "anyelement"}], "returns": "json", "never_null": true} +{"name": "score", "args": [{"type": "tokenlist"}, {"type": "string"}], "returns": "float64"} +{"name": "score_ngrams", "args": [{"type": "tokenlist"}, {"type": "string"}], "returns": "float64"} +{"name": "search", "args": [{"type": "tokenlist"}, {"type": "string"}], "returns": "bool"} +{"name": "search_ngrams", "args": [{"type": "tokenlist"}, {"type": "string"}], "returns": "bool"} +{"name": "search_substring", "args": [{"type": "tokenlist"}, {"type": "string"}], "returns": "bool"} +{"name": "sha1", "args": [{"type": "string"}], "returns": "bytes"} +{"name": "sha1", "args": [{"type": "bytes"}], "returns": "bytes"} +{"name": "sha256", "args": [{"type": "string"}], "returns": "bytes"} +{"name": "sha256", "args": [{"type": "bytes"}], "returns": "bytes"} +{"name": "sha512", "args": [{"type": "string"}], "returns": "bytes"} +{"name": "sha512", "args": [{"type": "bytes"}], "returns": "bytes"} +{"name": "sign", "args": [{"type": "int64"}], "returns": "int64"} +{"name": "sign", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "sign", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "sin", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "snippet", "args": [{"type": "string"}, {"type": "string"}], "returns": "json"} +{"name": "soundex", "args": [{"type": "string"}], "returns": "string"} +{"name": "split", "args": [{"type": "string"}], "returns": "array(string)"} +{"name": "split", "args": [{"type": "string"}, {"type": "string"}], "returns": "array(string)"} +{"name": "split", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "array(bytes)"} +{"name": "split_substr", "args": [{"type": "string"}, {"type": "string"}, {"type": "int64"}], "returns": "string"} +{"name": "split_substr", "args": [{"type": "string"}, {"type": "string"}, {"type": "int64"}, {"type": "int64"}], "returns": "string"} +{"name": "sqrt", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "sqrt", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "starts_with", "args": [{"type": "string"}, {"type": "string"}], "returns": "bool"} +{"name": "starts_with", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bool"} +{"name": "stddev", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} +{"name": "stddev_samp", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} +{"name": "string", "args": [{"type": "timestamp"}], "returns": "string"} +{"name": "string", "args": [{"type": "json"}], "returns": "string"} +{"name": "string", "args": [{"type": "timestamp"}, {"type": "string"}], "returns": "string"} +{"name": "string_agg", "kind": "a", "args": [{"type": "string"}], "returns": "string", "nullable": true} +{"name": "string_agg", "kind": "a", "args": [{"type": "bytes"}], "returns": "bytes", "nullable": true} +{"name": "string_agg", "kind": "a", "args": [{"type": "string"}, {"type": "string"}], "returns": "string", "nullable": true} +{"name": "string_agg", "kind": "a", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes", "nullable": true} +{"name": "strpos", "args": [{"type": "string"}, {"type": "string"}], "returns": "int64"} +{"name": "strpos", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "int64"} +{"name": "substr", "args": [{"type": "string"}, {"type": "int64"}], "returns": "string"} +{"name": "substr", "args": [{"type": "bytes"}, {"type": "int64"}], "returns": "bytes"} +{"name": "substr", "args": [{"type": "string"}, {"type": "int64"}, {"type": "int64"}], "returns": "string"} +{"name": "substr", "args": [{"type": "bytes"}, {"type": "int64"}, {"type": "int64"}], "returns": "bytes"} +{"name": "substring", "args": [{"type": "string"}, {"type": "int64"}], "returns": "string"} +{"name": "substring", "args": [{"type": "string"}, {"type": "int64"}, {"type": "int64"}], "returns": "string"} +{"name": "sum", "kind": "a", "args": [{"type": "int64"}], "returns": "int64", "nullable": true} +{"name": "sum", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} +{"name": "sum", "kind": "a", "args": [{"type": "float32"}], "returns": "float64", "nullable": true} +{"name": "sum", "kind": "a", "args": [{"type": "numeric"}], "returns": "numeric", "nullable": true} +{"name": "sum", "kind": "a", "args": [{"type": "interval"}], "returns": "interval", "nullable": true} +{"name": "tan", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "timestamp", "args": [{"type": "string"}], "returns": "timestamp"} +{"name": "timestamp", "args": [{"type": "date"}], "returns": "timestamp"} +{"name": "timestamp", "args": [{"type": "string"}, {"type": "string"}], "returns": "timestamp"} +{"name": "timestamp", "args": [{"type": "date"}, {"type": "string"}], "returns": "timestamp"} +{"name": "timestamp_add", "args": [{"type": "timestamp"}, {"type": "interval"}], "returns": "timestamp"} +{"name": "timestamp_diff", "args": [{"type": "timestamp"}, {"type": "timestamp"}, {"type": "string"}], "returns": "int64"} +{"name": "timestamp_micros", "args": [{"type": "int64"}], "returns": "timestamp"} +{"name": "timestamp_millis", "args": [{"type": "int64"}], "returns": "timestamp"} +{"name": "timestamp_seconds", "args": [{"type": "int64"}], "returns": "timestamp"} +{"name": "timestamp_sub", "args": [{"type": "timestamp"}, {"type": "interval"}], "returns": "timestamp"} +{"name": "timestamp_trunc", "args": [{"type": "timestamp"}, {"type": "string"}], "returns": "timestamp"} +{"name": "timestamp_trunc", "args": [{"type": "timestamp"}, {"type": "string"}, {"type": "string"}], "returns": "timestamp"} +{"name": "to_base32", "args": [{"type": "bytes"}], "returns": "string"} +{"name": "to_base64", "args": [{"type": "bytes"}], "returns": "string"} +{"name": "to_code_points", "args": [{"type": "string"}], "returns": "array(int64)"} +{"name": "to_code_points", "args": [{"type": "bytes"}], "returns": "array(int64)"} +{"name": "to_hex", "args": [{"type": "bytes"}], "returns": "string"} +{"name": "to_json", "args": [{"type": "anyelement"}], "returns": "json", "never_null": true} +{"name": "to_json_string", "args": [{"type": "json"}], "returns": "string", "never_null": true} +{"name": "trim", "args": [{"type": "string"}], "returns": "string"} +{"name": "trim", "args": [{"type": "string"}, {"type": "string"}], "returns": "string"} +{"name": "trim", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} +{"name": "trunc", "args": [{"type": "float64"}], "returns": "float64"} +{"name": "trunc", "args": [{"type": "numeric"}], "returns": "numeric"} +{"name": "trunc", "args": [{"type": "float64"}, {"type": "int64"}], "returns": "float64"} +{"name": "trunc", "args": [{"type": "numeric"}, {"type": "int64"}], "returns": "numeric"} +{"name": "unix_date", "args": [{"type": "date"}], "returns": "int64"} +{"name": "unix_micros", "args": [{"type": "timestamp"}], "returns": "int64"} +{"name": "unix_millis", "args": [{"type": "timestamp"}], "returns": "int64"} +{"name": "unix_seconds", "args": [{"type": "timestamp"}], "returns": "int64"} {"name": "unnest", "args": [{"type": "anyarray"}], "returns": "anyelement", "nullable": true} +{"name": "upper", "args": [{"type": "string"}], "returns": "string"} +{"name": "upper", "args": [{"type": "bytes"}], "returns": "bytes"} +{"name": "var_samp", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} +{"name": "variance", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} diff --git a/internal/engine/googlesql/dialect/operators.jsonl b/internal/engine/googlesql/dialect/operators.jsonl index 5967f9f055..cbf84bbe8d 100644 --- a/internal/engine/googlesql/dialect/operators.jsonl +++ b/internal/engine/googlesql/dialect/operators.jsonl @@ -1 +1,83 @@ +{"name": "+", "left": "int64", "right": "int64", "result": "int64"} +{"name": "+", "left": "int64", "right": "float64", "result": "float64"} +{"name": "+", "left": "int64", "right": "float32", "result": "float64"} +{"name": "+", "left": "int64", "right": "numeric", "result": "numeric"} +{"name": "+", "left": "float64", "right": "int64", "result": "float64"} +{"name": "+", "left": "float64", "right": "float64", "result": "float64"} +{"name": "+", "left": "float64", "right": "float32", "result": "float64"} +{"name": "+", "left": "float64", "right": "numeric", "result": "float64"} +{"name": "+", "left": "float32", "right": "int64", "result": "float64"} +{"name": "+", "left": "float32", "right": "float64", "result": "float64"} +{"name": "+", "left": "float32", "right": "float32", "result": "float64"} +{"name": "+", "left": "float32", "right": "numeric", "result": "float64"} +{"name": "+", "left": "numeric", "right": "int64", "result": "numeric"} +{"name": "+", "left": "numeric", "right": "float64", "result": "float64"} +{"name": "+", "left": "numeric", "right": "float32", "result": "float64"} +{"name": "+", "left": "numeric", "right": "numeric", "result": "numeric"} +{"name": "-", "left": "int64", "right": "int64", "result": "int64"} +{"name": "-", "left": "int64", "right": "float64", "result": "float64"} +{"name": "-", "left": "int64", "right": "float32", "result": "float64"} +{"name": "-", "left": "int64", "right": "numeric", "result": "numeric"} +{"name": "-", "left": "float64", "right": "int64", "result": "float64"} +{"name": "-", "left": "float64", "right": "float64", "result": "float64"} +{"name": "-", "left": "float64", "right": "float32", "result": "float64"} +{"name": "-", "left": "float64", "right": "numeric", "result": "float64"} +{"name": "-", "left": "float32", "right": "int64", "result": "float64"} +{"name": "-", "left": "float32", "right": "float64", "result": "float64"} +{"name": "-", "left": "float32", "right": "float32", "result": "float64"} +{"name": "-", "left": "float32", "right": "numeric", "result": "float64"} +{"name": "-", "left": "numeric", "right": "int64", "result": "numeric"} +{"name": "-", "left": "numeric", "right": "float64", "result": "float64"} +{"name": "-", "left": "numeric", "right": "float32", "result": "float64"} +{"name": "-", "left": "numeric", "right": "numeric", "result": "numeric"} +{"name": "*", "left": "int64", "right": "int64", "result": "int64"} +{"name": "*", "left": "int64", "right": "float64", "result": "float64"} +{"name": "*", "left": "int64", "right": "float32", "result": "float64"} +{"name": "*", "left": "int64", "right": "numeric", "result": "numeric"} +{"name": "*", "left": "float64", "right": "int64", "result": "float64"} +{"name": "*", "left": "float64", "right": "float64", "result": "float64"} +{"name": "*", "left": "float64", "right": "float32", "result": "float64"} +{"name": "*", "left": "float64", "right": "numeric", "result": "float64"} +{"name": "*", "left": "float32", "right": "int64", "result": "float64"} +{"name": "*", "left": "float32", "right": "float64", "result": "float64"} +{"name": "*", "left": "float32", "right": "float32", "result": "float64"} +{"name": "*", "left": "float32", "right": "numeric", "result": "float64"} +{"name": "*", "left": "numeric", "right": "int64", "result": "numeric"} +{"name": "*", "left": "numeric", "right": "float64", "result": "float64"} +{"name": "*", "left": "numeric", "right": "float32", "result": "float64"} +{"name": "*", "left": "numeric", "right": "numeric", "result": "numeric"} +{"name": "/", "left": "int64", "right": "int64", "result": "float64"} +{"name": "/", "left": "int64", "right": "float64", "result": "float64"} +{"name": "/", "left": "int64", "right": "float32", "result": "float64"} +{"name": "/", "left": "int64", "right": "numeric", "result": "numeric"} +{"name": "/", "left": "float64", "right": "int64", "result": "float64"} +{"name": "/", "left": "float64", "right": "float64", "result": "float64"} +{"name": "/", "left": "float64", "right": "float32", "result": "float64"} +{"name": "/", "left": "float64", "right": "numeric", "result": "float64"} +{"name": "/", "left": "float32", "right": "int64", "result": "float64"} +{"name": "/", "left": "float32", "right": "float64", "result": "float64"} +{"name": "/", "left": "float32", "right": "float32", "result": "float64"} +{"name": "/", "left": "float32", "right": "numeric", "result": "float64"} +{"name": "/", "left": "numeric", "right": "int64", "result": "numeric"} +{"name": "/", "left": "numeric", "right": "float64", "result": "float64"} +{"name": "/", "left": "numeric", "right": "float32", "result": "float64"} +{"name": "/", "left": "numeric", "right": "numeric", "result": "numeric"} {"name": "||", "left": "string", "right": "string", "result": "string"} +{"name": "||", "left": "bytes", "right": "bytes", "result": "bytes"} +{"name": "+", "left": "timestamp", "right": "interval", "result": "timestamp"} +{"name": "-", "left": "timestamp", "right": "interval", "result": "timestamp"} +{"name": "+", "left": "interval", "right": "timestamp", "result": "timestamp"} +{"name": "-", "left": "date", "right": "date", "result": "interval"} +{"name": "-", "left": "timestamp", "right": "timestamp", "result": "interval"} +{"name": "+", "left": "interval", "right": "interval", "result": "interval"} +{"name": "-", "left": "interval", "right": "interval", "result": "interval"} +{"name": "*", "left": "interval", "right": "int64", "result": "interval"} +{"name": "/", "left": "interval", "right": "int64", "result": "interval"} +{"name": "&", "left": "int64", "right": "int64", "result": "int64"} +{"name": "|", "left": "int64", "right": "int64", "result": "int64"} +{"name": "^", "left": "int64", "right": "int64", "result": "int64"} +{"name": "<<", "left": "int64", "right": "int64", "result": "int64"} +{"name": ">>", "left": "int64", "right": "int64", "result": "int64"} +{"name": "&", "left": "bytes", "right": "bytes", "result": "bytes"} +{"name": "|", "left": "bytes", "right": "bytes", "result": "bytes"} +{"name": "^", "left": "bytes", "right": "bytes", "result": "bytes"} From 8551ef18ffab1a5992e771ccc048d36746ae5d33 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 19:53:54 +0000 Subject: [PATCH 4/4] goldeneye: generate Spanner's types, functions and operators Spanner keeps no catalog of its library, so goldeneye now reads it by asking. spanner/library.jsonl lists the function signatures to try; each is compiled in PLAN mode against a probe database, and the ones Spanner accepts are written to functions.jsonl with the type it reports. A polymorphic signature is tried with INT64 and STRING and its result must hold for both. Calls a select list cannot make are compiled the way they can be: SEARCH in a WHERE clause, SCORE over a search index, PENDING_COMMIT_TIMESTAMP in an INSERT. operators.jsonl is every binary operator Spanner accepts over the operands tried, and types.jsonl the types the dialect lists, each one Spanner has checked by a cast; the GoogleSQL types only BigQuery has are kept as they are. The generated files hold the same records the seed already did; they are rewritten in goldeneye's compact form, and TestDialect now checks all four files against Spanner. The dialect package's Function gains never_null, which the seed already reads. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L8QoGi6VRtADkb4jL3BFHP --- .../engine/googlesql/dialect/functions.jsonl | 582 +++++++++--------- .../engine/googlesql/dialect/operators.jsonl | 166 ++--- internal/engine/googlesql/dialect/types.jsonl | 46 +- internal/goldeneye/README.md | 12 +- internal/goldeneye/dialect/dialect.go | 3 + internal/goldeneye/spanner/library.go | 428 +++++++++++++ internal/goldeneye/spanner/library.jsonl | 322 ++++++++++ internal/goldeneye/spanner/spanner.go | 35 +- 8 files changed, 1187 insertions(+), 407 deletions(-) create mode 100644 internal/goldeneye/spanner/library.go create mode 100644 internal/goldeneye/spanner/library.jsonl diff --git a/internal/engine/googlesql/dialect/functions.jsonl b/internal/engine/googlesql/dialect/functions.jsonl index 6090fbcfde..e01d9a0fba 100644 --- a/internal/engine/googlesql/dialect/functions.jsonl +++ b/internal/engine/googlesql/dialect/functions.jsonl @@ -1,291 +1,291 @@ -{"name": "abs", "args": [{"type": "int64"}], "returns": "int64"} -{"name": "abs", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "abs", "args": [{"type": "float32"}], "returns": "float32"} -{"name": "abs", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "acos", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "any_value", "kind": "a", "args": [{"type": "anyelement"}], "returns": "$1", "nullable": true} -{"name": "array_agg", "kind": "a", "args": [{"type": "anyelement"}], "returns": "array($1)", "nullable": true} -{"name": "array_concat", "args": [{"type": "anyarray"}, {"type": "anyarray"}], "returns": "$1"} -{"name": "array_concat_agg", "kind": "a", "args": [{"type": "anyarray"}], "returns": "$1", "nullable": true} -{"name": "array_first", "args": [{"type": "anyarray"}], "returns": "anyelement", "nullable": true} -{"name": "array_includes", "args": [{"type": "anyarray"}, {"type": "anyelement"}], "returns": "bool"} -{"name": "array_includes_all", "args": [{"type": "anyarray"}, {"type": "anyarray"}], "returns": "bool"} -{"name": "array_includes_any", "args": [{"type": "anyarray"}, {"type": "anyarray"}], "returns": "bool"} -{"name": "array_is_distinct", "args": [{"type": "anyarray"}], "returns": "bool"} -{"name": "array_last", "args": [{"type": "anyarray"}], "returns": "anyelement", "nullable": true} -{"name": "array_length", "args": [{"type": "anyarray"}], "returns": "int64"} -{"name": "array_max", "args": [{"type": "anyarray"}], "returns": "anyelement", "nullable": true} -{"name": "array_min", "args": [{"type": "anyarray"}], "returns": "anyelement", "nullable": true} -{"name": "array_reverse", "args": [{"type": "anyarray"}], "returns": "$1"} -{"name": "array_slice", "args": [{"type": "anyarray"}, {"type": "int64"}, {"type": "int64"}], "returns": "$1"} -{"name": "array_to_string", "args": [{"type": "array(string)"}, {"type": "string"}], "returns": "string"} -{"name": "array_to_string", "args": [{"type": "array(bytes)"}, {"type": "bytes"}], "returns": "bytes"} -{"name": "array_to_string", "args": [{"type": "array(string)"}, {"type": "string"}, {"type": "string"}], "returns": "string"} -{"name": "asin", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "atan", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "atan2", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64"} -{"name": "avg", "kind": "a", "args": [{"type": "int64"}], "returns": "float64", "nullable": true} -{"name": "avg", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} -{"name": "avg", "kind": "a", "args": [{"type": "float32"}], "returns": "float64", "nullable": true} -{"name": "avg", "kind": "a", "args": [{"type": "numeric"}], "returns": "numeric", "nullable": true} -{"name": "avg", "kind": "a", "args": [{"type": "interval"}], "returns": "interval", "nullable": true} -{"name": "bit_and", "kind": "a", "args": [{"type": "int64"}], "returns": "int64", "nullable": true} -{"name": "bit_count", "args": [{"type": "int64"}], "returns": "int64"} -{"name": "bit_count", "args": [{"type": "bytes"}], "returns": "int64"} -{"name": "bit_or", "kind": "a", "args": [{"type": "int64"}], "returns": "int64", "nullable": true} -{"name": "bit_reverse", "args": [{"type": "int64"}, {"type": "bool"}], "returns": "int64"} -{"name": "bit_xor", "kind": "a", "args": [{"type": "int64"}], "returns": "int64", "nullable": true} -{"name": "bool", "args": [{"type": "json"}], "returns": "bool"} -{"name": "byte_length", "args": [{"type": "string"}], "returns": "int64"} -{"name": "byte_length", "args": [{"type": "bytes"}], "returns": "int64"} -{"name": "ceil", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "ceil", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "ceiling", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "ceiling", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "char_length", "args": [{"type": "string"}], "returns": "int64"} -{"name": "character_length", "args": [{"type": "string"}], "returns": "int64"} -{"name": "code_points_to_bytes", "args": [{"type": "array(int64)"}], "returns": "bytes"} -{"name": "code_points_to_string", "args": [{"type": "array(int64)"}], "returns": "string"} -{"name": "concat", "args": [{"type": "string"}, {"type": "string"}], "returns": "string"} -{"name": "concat", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} -{"name": "cos", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "cosine_distance", "args": [{"type": "array(float64)"}, {"type": "array(float64)"}], "returns": "float64"} -{"name": "cosine_distance", "args": [{"type": "array(float32)"}, {"type": "array(float32)"}], "returns": "float64"} -{"name": "count", "kind": "a", "args": [{"type": "anyelement"}], "returns": "int64", "never_null": true} -{"name": "countif", "kind": "a", "args": [{"type": "bool"}], "returns": "int64", "never_null": true} -{"name": "current_date", "returns": "date"} -{"name": "current_timestamp", "returns": "timestamp"} -{"name": "date", "args": [{"type": "timestamp"}], "returns": "date"} -{"name": "date", "args": [{"type": "timestamp"}, {"type": "string"}], "returns": "date"} -{"name": "date", "args": [{"type": "int64"}, {"type": "int64"}, {"type": "int64"}], "returns": "date"} -{"name": "date_add", "args": [{"type": "date"}, {"type": "interval"}], "returns": "date"} -{"name": "date_diff", "args": [{"type": "date"}, {"type": "date"}, {"type": "string"}], "returns": "int64"} -{"name": "date_from_unix_date", "args": [{"type": "int64"}], "returns": "date"} -{"name": "date_sub", "args": [{"type": "date"}, {"type": "interval"}], "returns": "date"} -{"name": "date_trunc", "args": [{"type": "date"}, {"type": "string"}], "returns": "date"} -{"name": "div", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "int64"} -{"name": "div", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric"} -{"name": "dot_product", "args": [{"type": "array(float64)"}, {"type": "array(float64)"}], "returns": "float64"} -{"name": "dot_product", "args": [{"type": "array(float32)"}, {"type": "array(float32)"}], "returns": "float64"} -{"name": "dot_product", "args": [{"type": "array(int64)"}, {"type": "array(int64)"}], "returns": "float64"} -{"name": "ends_with", "args": [{"type": "string"}, {"type": "string"}], "returns": "bool"} -{"name": "ends_with", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bool"} -{"name": "euclidean_distance", "args": [{"type": "array(float64)"}, {"type": "array(float64)"}], "returns": "float64"} -{"name": "euclidean_distance", "args": [{"type": "array(float32)"}, {"type": "array(float32)"}], "returns": "float64"} -{"name": "exp", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "exp", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "extract", "args": [{"type": "timestamp"}], "returns": "int64"} -{"name": "extract", "args": [{"type": "date"}], "returns": "int64"} -{"name": "extract", "args": [{"type": "timestamp"}, {"type": "string"}], "returns": "int64"} -{"name": "farm_fingerprint", "args": [{"type": "string"}], "returns": "int64"} -{"name": "farm_fingerprint", "args": [{"type": "bytes"}], "returns": "int64"} -{"name": "float32", "args": [{"type": "json"}], "returns": "float32"} -{"name": "float64", "args": [{"type": "json"}], "returns": "float64"} -{"name": "floor", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "floor", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "format", "args": [{"type": "string"}], "returns": "string"} -{"name": "format_date", "args": [{"type": "string"}, {"type": "date"}], "returns": "string"} -{"name": "format_timestamp", "args": [{"type": "string"}, {"type": "timestamp"}], "returns": "string"} -{"name": "format_timestamp", "args": [{"type": "string"}, {"type": "timestamp"}, {"type": "string"}], "returns": "string"} -{"name": "from_base32", "args": [{"type": "string"}], "returns": "bytes"} -{"name": "from_base64", "args": [{"type": "string"}], "returns": "bytes"} -{"name": "from_hex", "args": [{"type": "string"}], "returns": "bytes"} -{"name": "generate_array", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "array(int64)"} -{"name": "generate_array", "args": [{"type": "int64"}, {"type": "int64"}, {"type": "int64"}], "returns": "array(int64)"} -{"name": "generate_array", "args": [{"type": "float64"}, {"type": "float64"}, {"type": "float64"}], "returns": "array(float64)"} -{"name": "generate_date_array", "args": [{"type": "date"}, {"type": "date"}], "returns": "array(date)"} -{"name": "generate_date_array", "args": [{"type": "date"}, {"type": "date"}, {"type": "interval"}], "returns": "array(date)"} -{"name": "generate_uuid", "returns": "string", "never_null": true} -{"name": "get_next_sequence_value", "args": [{"type": "anyelement"}], "returns": "int64", "never_null": true} -{"name": "ieee_divide", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64"} -{"name": "int64", "args": [{"type": "json"}], "returns": "int64"} -{"name": "is_inf", "args": [{"type": "float64"}], "returns": "bool"} -{"name": "is_nan", "args": [{"type": "float64"}], "returns": "bool"} -{"name": "json_array", "args": [{"type": "anyelement"}], "returns": "json"} -{"name": "json_array_append", "args": [{"type": "json"}, {"type": "string"}, {"type": "anyelement"}], "returns": "json"} -{"name": "json_array_insert", "args": [{"type": "json"}, {"type": "string"}, {"type": "anyelement"}], "returns": "json"} -{"name": "json_contains", "args": [{"type": "json"}, {"type": "json"}], "returns": "bool"} -{"name": "json_keys", "args": [{"type": "json"}], "returns": "array(string)"} -{"name": "json_object", "args": [{"type": "string"}, {"type": "anyelement"}], "returns": "json"} -{"name": "json_query", "args": [{"type": "json"}, {"type": "string"}], "returns": "json", "nullable": true} -{"name": "json_query", "args": [{"type": "string"}, {"type": "string"}], "returns": "string", "nullable": true} -{"name": "json_query_array", "args": [{"type": "json"}], "returns": "array(json)", "nullable": true} -{"name": "json_query_array", "args": [{"type": "json"}, {"type": "string"}], "returns": "array(json)", "nullable": true} -{"name": "json_remove", "args": [{"type": "json"}, {"type": "string"}], "returns": "json"} -{"name": "json_set", "args": [{"type": "json"}, {"type": "string"}, {"type": "anyelement"}], "returns": "json"} -{"name": "json_strip_nulls", "args": [{"type": "json"}], "returns": "json"} -{"name": "json_type", "args": [{"type": "json"}], "returns": "string"} -{"name": "json_value", "args": [{"type": "json"}, {"type": "string"}], "returns": "string", "nullable": true} -{"name": "json_value", "args": [{"type": "string"}, {"type": "string"}], "returns": "string", "nullable": true} -{"name": "json_value_array", "args": [{"type": "json"}], "returns": "array(string)", "nullable": true} -{"name": "json_value_array", "args": [{"type": "json"}, {"type": "string"}], "returns": "array(string)", "nullable": true} -{"name": "justify_days", "args": [{"type": "interval"}], "returns": "interval"} -{"name": "justify_hours", "args": [{"type": "interval"}], "returns": "interval"} -{"name": "justify_interval", "args": [{"type": "interval"}], "returns": "interval"} -{"name": "lax_bool", "args": [{"type": "json"}], "returns": "bool", "nullable": true} -{"name": "lax_float64", "args": [{"type": "json"}], "returns": "float64", "nullable": true} -{"name": "lax_int64", "args": [{"type": "json"}], "returns": "int64", "nullable": true} -{"name": "lax_string", "args": [{"type": "json"}], "returns": "string", "nullable": true} -{"name": "length", "args": [{"type": "string"}], "returns": "int64"} -{"name": "length", "args": [{"type": "bytes"}], "returns": "int64"} -{"name": "ln", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "ln", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "log", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "log", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "log", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64"} -{"name": "log10", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "log10", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "logical_and", "kind": "a", "args": [{"type": "bool"}], "returns": "bool", "nullable": true} -{"name": "logical_or", "kind": "a", "args": [{"type": "bool"}], "returns": "bool", "nullable": true} -{"name": "lower", "args": [{"type": "string"}], "returns": "string"} -{"name": "lower", "args": [{"type": "bytes"}], "returns": "bytes"} -{"name": "lpad", "args": [{"type": "string"}, {"type": "int64"}], "returns": "string"} -{"name": "lpad", "args": [{"type": "string"}, {"type": "int64"}, {"type": "string"}], "returns": "string"} -{"name": "lpad", "args": [{"type": "bytes"}, {"type": "int64"}, {"type": "bytes"}], "returns": "bytes"} -{"name": "ltrim", "args": [{"type": "string"}], "returns": "string"} -{"name": "ltrim", "args": [{"type": "string"}, {"type": "string"}], "returns": "string"} -{"name": "ltrim", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} -{"name": "make_interval", "args": [{"type": "int64"}, {"type": "int64"}, {"type": "int64"}], "returns": "interval"} -{"name": "max", "kind": "a", "args": [{"type": "anyelement"}], "returns": "$1", "nullable": true} -{"name": "md5", "args": [{"type": "string"}], "returns": "bytes"} -{"name": "md5", "args": [{"type": "bytes"}], "returns": "bytes"} -{"name": "min", "kind": "a", "args": [{"type": "anyelement"}], "returns": "$1", "nullable": true} -{"name": "mod", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "int64"} -{"name": "mod", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric"} -{"name": "new_uuid", "returns": "uuid", "never_null": true} -{"name": "normalize", "args": [{"type": "string"}], "returns": "string"} -{"name": "normalize_and_casefold", "args": [{"type": "string"}], "returns": "string"} -{"name": "parse_date", "args": [{"type": "string"}, {"type": "string"}], "returns": "date"} -{"name": "parse_json", "args": [{"type": "string"}], "returns": "json"} -{"name": "parse_timestamp", "args": [{"type": "string"}, {"type": "string"}], "returns": "timestamp"} -{"name": "parse_timestamp", "args": [{"type": "string"}, {"type": "string"}, {"type": "string"}], "returns": "timestamp"} -{"name": "pending_commit_timestamp", "returns": "timestamp", "never_null": true} -{"name": "pow", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64"} -{"name": "pow", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric"} -{"name": "power", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64"} -{"name": "power", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric"} -{"name": "regexp_contains", "args": [{"type": "string"}, {"type": "string"}], "returns": "bool"} -{"name": "regexp_contains", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bool"} -{"name": "regexp_extract", "args": [{"type": "string"}, {"type": "string"}], "returns": "string", "nullable": true} -{"name": "regexp_extract", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes", "nullable": true} -{"name": "regexp_extract_all", "args": [{"type": "string"}, {"type": "string"}], "returns": "array(string)"} -{"name": "regexp_extract_all", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "array(bytes)"} -{"name": "regexp_replace", "args": [{"type": "string"}, {"type": "string"}, {"type": "string"}], "returns": "string"} -{"name": "regexp_replace", "args": [{"type": "bytes"}, {"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} -{"name": "repeat", "args": [{"type": "string"}, {"type": "int64"}], "returns": "string"} -{"name": "repeat", "args": [{"type": "bytes"}, {"type": "int64"}], "returns": "bytes"} -{"name": "replace", "args": [{"type": "string"}, {"type": "string"}, {"type": "string"}], "returns": "string"} -{"name": "replace", "args": [{"type": "bytes"}, {"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} -{"name": "reverse", "args": [{"type": "string"}], "returns": "string"} -{"name": "reverse", "args": [{"type": "bytes"}], "returns": "bytes"} -{"name": "round", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "round", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "round", "args": [{"type": "float64"}, {"type": "int64"}], "returns": "float64"} -{"name": "round", "args": [{"type": "numeric"}, {"type": "int64"}], "returns": "numeric"} -{"name": "rpad", "args": [{"type": "string"}, {"type": "int64"}], "returns": "string"} -{"name": "rpad", "args": [{"type": "string"}, {"type": "int64"}, {"type": "string"}], "returns": "string"} -{"name": "rpad", "args": [{"type": "bytes"}, {"type": "int64"}, {"type": "bytes"}], "returns": "bytes"} -{"name": "rtrim", "args": [{"type": "string"}], "returns": "string"} -{"name": "rtrim", "args": [{"type": "string"}, {"type": "string"}], "returns": "string"} -{"name": "rtrim", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} -{"name": "safe_add", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "int64", "nullable": true} -{"name": "safe_add", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64", "nullable": true} -{"name": "safe_add", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric", "nullable": true} -{"name": "safe_convert_bytes_to_string", "args": [{"type": "bytes"}], "returns": "string"} -{"name": "safe_divide", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "float64", "nullable": true} -{"name": "safe_divide", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64", "nullable": true} -{"name": "safe_divide", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric", "nullable": true} -{"name": "safe_multiply", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "int64", "nullable": true} -{"name": "safe_multiply", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64", "nullable": true} -{"name": "safe_multiply", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric", "nullable": true} -{"name": "safe_negate", "args": [{"type": "int64"}], "returns": "int64", "nullable": true} -{"name": "safe_negate", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} -{"name": "safe_negate", "args": [{"type": "numeric"}], "returns": "numeric", "nullable": true} -{"name": "safe_subtract", "args": [{"type": "int64"}, {"type": "int64"}], "returns": "int64", "nullable": true} -{"name": "safe_subtract", "args": [{"type": "float64"}, {"type": "float64"}], "returns": "float64", "nullable": true} -{"name": "safe_subtract", "args": [{"type": "numeric"}, {"type": "numeric"}], "returns": "numeric", "nullable": true} -{"name": "safe_to_json", "args": [{"type": "anyelement"}], "returns": "json", "never_null": true} -{"name": "score", "args": [{"type": "tokenlist"}, {"type": "string"}], "returns": "float64"} -{"name": "score_ngrams", "args": [{"type": "tokenlist"}, {"type": "string"}], "returns": "float64"} -{"name": "search", "args": [{"type": "tokenlist"}, {"type": "string"}], "returns": "bool"} -{"name": "search_ngrams", "args": [{"type": "tokenlist"}, {"type": "string"}], "returns": "bool"} -{"name": "search_substring", "args": [{"type": "tokenlist"}, {"type": "string"}], "returns": "bool"} -{"name": "sha1", "args": [{"type": "string"}], "returns": "bytes"} -{"name": "sha1", "args": [{"type": "bytes"}], "returns": "bytes"} -{"name": "sha256", "args": [{"type": "string"}], "returns": "bytes"} -{"name": "sha256", "args": [{"type": "bytes"}], "returns": "bytes"} -{"name": "sha512", "args": [{"type": "string"}], "returns": "bytes"} -{"name": "sha512", "args": [{"type": "bytes"}], "returns": "bytes"} -{"name": "sign", "args": [{"type": "int64"}], "returns": "int64"} -{"name": "sign", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "sign", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "sin", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "snippet", "args": [{"type": "string"}, {"type": "string"}], "returns": "json"} -{"name": "soundex", "args": [{"type": "string"}], "returns": "string"} -{"name": "split", "args": [{"type": "string"}], "returns": "array(string)"} -{"name": "split", "args": [{"type": "string"}, {"type": "string"}], "returns": "array(string)"} -{"name": "split", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "array(bytes)"} -{"name": "split_substr", "args": [{"type": "string"}, {"type": "string"}, {"type": "int64"}], "returns": "string"} -{"name": "split_substr", "args": [{"type": "string"}, {"type": "string"}, {"type": "int64"}, {"type": "int64"}], "returns": "string"} -{"name": "sqrt", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "sqrt", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "starts_with", "args": [{"type": "string"}, {"type": "string"}], "returns": "bool"} -{"name": "starts_with", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bool"} -{"name": "stddev", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} -{"name": "stddev_samp", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} -{"name": "string", "args": [{"type": "timestamp"}], "returns": "string"} -{"name": "string", "args": [{"type": "json"}], "returns": "string"} -{"name": "string", "args": [{"type": "timestamp"}, {"type": "string"}], "returns": "string"} -{"name": "string_agg", "kind": "a", "args": [{"type": "string"}], "returns": "string", "nullable": true} -{"name": "string_agg", "kind": "a", "args": [{"type": "bytes"}], "returns": "bytes", "nullable": true} -{"name": "string_agg", "kind": "a", "args": [{"type": "string"}, {"type": "string"}], "returns": "string", "nullable": true} -{"name": "string_agg", "kind": "a", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes", "nullable": true} -{"name": "strpos", "args": [{"type": "string"}, {"type": "string"}], "returns": "int64"} -{"name": "strpos", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "int64"} -{"name": "substr", "args": [{"type": "string"}, {"type": "int64"}], "returns": "string"} -{"name": "substr", "args": [{"type": "bytes"}, {"type": "int64"}], "returns": "bytes"} -{"name": "substr", "args": [{"type": "string"}, {"type": "int64"}, {"type": "int64"}], "returns": "string"} -{"name": "substr", "args": [{"type": "bytes"}, {"type": "int64"}, {"type": "int64"}], "returns": "bytes"} -{"name": "substring", "args": [{"type": "string"}, {"type": "int64"}], "returns": "string"} -{"name": "substring", "args": [{"type": "string"}, {"type": "int64"}, {"type": "int64"}], "returns": "string"} -{"name": "sum", "kind": "a", "args": [{"type": "int64"}], "returns": "int64", "nullable": true} -{"name": "sum", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} -{"name": "sum", "kind": "a", "args": [{"type": "float32"}], "returns": "float64", "nullable": true} -{"name": "sum", "kind": "a", "args": [{"type": "numeric"}], "returns": "numeric", "nullable": true} -{"name": "sum", "kind": "a", "args": [{"type": "interval"}], "returns": "interval", "nullable": true} -{"name": "tan", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "timestamp", "args": [{"type": "string"}], "returns": "timestamp"} -{"name": "timestamp", "args": [{"type": "date"}], "returns": "timestamp"} -{"name": "timestamp", "args": [{"type": "string"}, {"type": "string"}], "returns": "timestamp"} -{"name": "timestamp", "args": [{"type": "date"}, {"type": "string"}], "returns": "timestamp"} -{"name": "timestamp_add", "args": [{"type": "timestamp"}, {"type": "interval"}], "returns": "timestamp"} -{"name": "timestamp_diff", "args": [{"type": "timestamp"}, {"type": "timestamp"}, {"type": "string"}], "returns": "int64"} -{"name": "timestamp_micros", "args": [{"type": "int64"}], "returns": "timestamp"} -{"name": "timestamp_millis", "args": [{"type": "int64"}], "returns": "timestamp"} -{"name": "timestamp_seconds", "args": [{"type": "int64"}], "returns": "timestamp"} -{"name": "timestamp_sub", "args": [{"type": "timestamp"}, {"type": "interval"}], "returns": "timestamp"} -{"name": "timestamp_trunc", "args": [{"type": "timestamp"}, {"type": "string"}], "returns": "timestamp"} -{"name": "timestamp_trunc", "args": [{"type": "timestamp"}, {"type": "string"}, {"type": "string"}], "returns": "timestamp"} -{"name": "to_base32", "args": [{"type": "bytes"}], "returns": "string"} -{"name": "to_base64", "args": [{"type": "bytes"}], "returns": "string"} -{"name": "to_code_points", "args": [{"type": "string"}], "returns": "array(int64)"} -{"name": "to_code_points", "args": [{"type": "bytes"}], "returns": "array(int64)"} -{"name": "to_hex", "args": [{"type": "bytes"}], "returns": "string"} -{"name": "to_json", "args": [{"type": "anyelement"}], "returns": "json", "never_null": true} -{"name": "to_json_string", "args": [{"type": "json"}], "returns": "string", "never_null": true} -{"name": "trim", "args": [{"type": "string"}], "returns": "string"} -{"name": "trim", "args": [{"type": "string"}, {"type": "string"}], "returns": "string"} -{"name": "trim", "args": [{"type": "bytes"}, {"type": "bytes"}], "returns": "bytes"} -{"name": "trunc", "args": [{"type": "float64"}], "returns": "float64"} -{"name": "trunc", "args": [{"type": "numeric"}], "returns": "numeric"} -{"name": "trunc", "args": [{"type": "float64"}, {"type": "int64"}], "returns": "float64"} -{"name": "trunc", "args": [{"type": "numeric"}, {"type": "int64"}], "returns": "numeric"} -{"name": "unix_date", "args": [{"type": "date"}], "returns": "int64"} -{"name": "unix_micros", "args": [{"type": "timestamp"}], "returns": "int64"} -{"name": "unix_millis", "args": [{"type": "timestamp"}], "returns": "int64"} -{"name": "unix_seconds", "args": [{"type": "timestamp"}], "returns": "int64"} -{"name": "unnest", "args": [{"type": "anyarray"}], "returns": "anyelement", "nullable": true} -{"name": "upper", "args": [{"type": "string"}], "returns": "string"} -{"name": "upper", "args": [{"type": "bytes"}], "returns": "bytes"} -{"name": "var_samp", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} -{"name": "variance", "kind": "a", "args": [{"type": "float64"}], "returns": "float64", "nullable": true} +{"name":"abs","args":[{"type":"int64"}],"returns":"int64"} +{"name":"abs","args":[{"type":"float64"}],"returns":"float64"} +{"name":"abs","args":[{"type":"float32"}],"returns":"float32"} +{"name":"abs","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"acos","args":[{"type":"float64"}],"returns":"float64"} +{"name":"any_value","kind":"a","args":[{"type":"anyelement"}],"returns":"$1","nullable":true} +{"name":"array_agg","kind":"a","args":[{"type":"anyelement"}],"returns":"array($1)","nullable":true} +{"name":"array_concat","args":[{"type":"anyarray"},{"type":"anyarray"}],"returns":"$1"} +{"name":"array_concat_agg","kind":"a","args":[{"type":"anyarray"}],"returns":"$1","nullable":true} +{"name":"array_first","args":[{"type":"anyarray"}],"returns":"anyelement","nullable":true} +{"name":"array_includes","args":[{"type":"anyarray"},{"type":"anyelement"}],"returns":"bool"} +{"name":"array_includes_all","args":[{"type":"anyarray"},{"type":"anyarray"}],"returns":"bool"} +{"name":"array_includes_any","args":[{"type":"anyarray"},{"type":"anyarray"}],"returns":"bool"} +{"name":"array_is_distinct","args":[{"type":"anyarray"}],"returns":"bool"} +{"name":"array_last","args":[{"type":"anyarray"}],"returns":"anyelement","nullable":true} +{"name":"array_length","args":[{"type":"anyarray"}],"returns":"int64"} +{"name":"array_max","args":[{"type":"anyarray"}],"returns":"anyelement","nullable":true} +{"name":"array_min","args":[{"type":"anyarray"}],"returns":"anyelement","nullable":true} +{"name":"array_reverse","args":[{"type":"anyarray"}],"returns":"$1"} +{"name":"array_slice","args":[{"type":"anyarray"},{"type":"int64"},{"type":"int64"}],"returns":"$1"} +{"name":"array_to_string","args":[{"type":"array(string)"},{"type":"string"}],"returns":"string"} +{"name":"array_to_string","args":[{"type":"array(bytes)"},{"type":"bytes"}],"returns":"bytes"} +{"name":"array_to_string","args":[{"type":"array(string)"},{"type":"string"},{"type":"string"}],"returns":"string"} +{"name":"asin","args":[{"type":"float64"}],"returns":"float64"} +{"name":"atan","args":[{"type":"float64"}],"returns":"float64"} +{"name":"atan2","args":[{"type":"float64"},{"type":"float64"}],"returns":"float64"} +{"name":"avg","kind":"a","args":[{"type":"int64"}],"returns":"float64","nullable":true} +{"name":"avg","kind":"a","args":[{"type":"float64"}],"returns":"float64","nullable":true} +{"name":"avg","kind":"a","args":[{"type":"float32"}],"returns":"float64","nullable":true} +{"name":"avg","kind":"a","args":[{"type":"numeric"}],"returns":"numeric","nullable":true} +{"name":"avg","kind":"a","args":[{"type":"interval"}],"returns":"interval","nullable":true} +{"name":"bit_and","kind":"a","args":[{"type":"int64"}],"returns":"int64","nullable":true} +{"name":"bit_count","args":[{"type":"int64"}],"returns":"int64"} +{"name":"bit_count","args":[{"type":"bytes"}],"returns":"int64"} +{"name":"bit_or","kind":"a","args":[{"type":"int64"}],"returns":"int64","nullable":true} +{"name":"bit_reverse","args":[{"type":"int64"},{"type":"bool"}],"returns":"int64"} +{"name":"bit_xor","kind":"a","args":[{"type":"int64"}],"returns":"int64","nullable":true} +{"name":"bool","args":[{"type":"json"}],"returns":"bool"} +{"name":"byte_length","args":[{"type":"string"}],"returns":"int64"} +{"name":"byte_length","args":[{"type":"bytes"}],"returns":"int64"} +{"name":"ceil","args":[{"type":"float64"}],"returns":"float64"} +{"name":"ceil","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"ceiling","args":[{"type":"float64"}],"returns":"float64"} +{"name":"ceiling","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"char_length","args":[{"type":"string"}],"returns":"int64"} +{"name":"character_length","args":[{"type":"string"}],"returns":"int64"} +{"name":"code_points_to_bytes","args":[{"type":"array(int64)"}],"returns":"bytes"} +{"name":"code_points_to_string","args":[{"type":"array(int64)"}],"returns":"string"} +{"name":"concat","args":[{"type":"string"},{"type":"string"}],"returns":"string"} +{"name":"concat","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"bytes"} +{"name":"cos","args":[{"type":"float64"}],"returns":"float64"} +{"name":"cosine_distance","args":[{"type":"array(float64)"},{"type":"array(float64)"}],"returns":"float64"} +{"name":"cosine_distance","args":[{"type":"array(float32)"},{"type":"array(float32)"}],"returns":"float64"} +{"name":"count","kind":"a","args":[{"type":"anyelement"}],"returns":"int64","never_null":true} +{"name":"countif","kind":"a","args":[{"type":"bool"}],"returns":"int64","never_null":true} +{"name":"current_date","returns":"date"} +{"name":"current_timestamp","returns":"timestamp"} +{"name":"date","args":[{"type":"timestamp"}],"returns":"date"} +{"name":"date","args":[{"type":"timestamp"},{"type":"string"}],"returns":"date"} +{"name":"date","args":[{"type":"int64"},{"type":"int64"},{"type":"int64"}],"returns":"date"} +{"name":"date_add","args":[{"type":"date"},{"type":"interval"}],"returns":"date"} +{"name":"date_diff","args":[{"type":"date"},{"type":"date"},{"type":"string"}],"returns":"int64"} +{"name":"date_from_unix_date","args":[{"type":"int64"}],"returns":"date"} +{"name":"date_sub","args":[{"type":"date"},{"type":"interval"}],"returns":"date"} +{"name":"date_trunc","args":[{"type":"date"},{"type":"string"}],"returns":"date"} +{"name":"div","args":[{"type":"int64"},{"type":"int64"}],"returns":"int64"} +{"name":"div","args":[{"type":"numeric"},{"type":"numeric"}],"returns":"numeric"} +{"name":"dot_product","args":[{"type":"array(float64)"},{"type":"array(float64)"}],"returns":"float64"} +{"name":"dot_product","args":[{"type":"array(float32)"},{"type":"array(float32)"}],"returns":"float64"} +{"name":"dot_product","args":[{"type":"array(int64)"},{"type":"array(int64)"}],"returns":"float64"} +{"name":"ends_with","args":[{"type":"string"},{"type":"string"}],"returns":"bool"} +{"name":"ends_with","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"bool"} +{"name":"euclidean_distance","args":[{"type":"array(float64)"},{"type":"array(float64)"}],"returns":"float64"} +{"name":"euclidean_distance","args":[{"type":"array(float32)"},{"type":"array(float32)"}],"returns":"float64"} +{"name":"exp","args":[{"type":"float64"}],"returns":"float64"} +{"name":"exp","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"extract","args":[{"type":"timestamp"}],"returns":"int64"} +{"name":"extract","args":[{"type":"date"}],"returns":"int64"} +{"name":"extract","args":[{"type":"timestamp"},{"type":"string"}],"returns":"int64"} +{"name":"farm_fingerprint","args":[{"type":"string"}],"returns":"int64"} +{"name":"farm_fingerprint","args":[{"type":"bytes"}],"returns":"int64"} +{"name":"float32","args":[{"type":"json"}],"returns":"float32"} +{"name":"float64","args":[{"type":"json"}],"returns":"float64"} +{"name":"floor","args":[{"type":"float64"}],"returns":"float64"} +{"name":"floor","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"format","args":[{"type":"string"}],"returns":"string"} +{"name":"format_date","args":[{"type":"string"},{"type":"date"}],"returns":"string"} +{"name":"format_timestamp","args":[{"type":"string"},{"type":"timestamp"}],"returns":"string"} +{"name":"format_timestamp","args":[{"type":"string"},{"type":"timestamp"},{"type":"string"}],"returns":"string"} +{"name":"from_base32","args":[{"type":"string"}],"returns":"bytes"} +{"name":"from_base64","args":[{"type":"string"}],"returns":"bytes"} +{"name":"from_hex","args":[{"type":"string"}],"returns":"bytes"} +{"name":"generate_array","args":[{"type":"int64"},{"type":"int64"}],"returns":"array(int64)"} +{"name":"generate_array","args":[{"type":"int64"},{"type":"int64"},{"type":"int64"}],"returns":"array(int64)"} +{"name":"generate_array","args":[{"type":"float64"},{"type":"float64"},{"type":"float64"}],"returns":"array(float64)"} +{"name":"generate_date_array","args":[{"type":"date"},{"type":"date"}],"returns":"array(date)"} +{"name":"generate_date_array","args":[{"type":"date"},{"type":"date"},{"type":"interval"}],"returns":"array(date)"} +{"name":"generate_uuid","returns":"string","never_null":true} +{"name":"get_next_sequence_value","args":[{"type":"anyelement"}],"returns":"int64","never_null":true} +{"name":"ieee_divide","args":[{"type":"float64"},{"type":"float64"}],"returns":"float64"} +{"name":"int64","args":[{"type":"json"}],"returns":"int64"} +{"name":"is_inf","args":[{"type":"float64"}],"returns":"bool"} +{"name":"is_nan","args":[{"type":"float64"}],"returns":"bool"} +{"name":"json_array","args":[{"type":"anyelement"}],"returns":"json"} +{"name":"json_array_append","args":[{"type":"json"},{"type":"string"},{"type":"anyelement"}],"returns":"json"} +{"name":"json_array_insert","args":[{"type":"json"},{"type":"string"},{"type":"anyelement"}],"returns":"json"} +{"name":"json_contains","args":[{"type":"json"},{"type":"json"}],"returns":"bool"} +{"name":"json_keys","args":[{"type":"json"}],"returns":"array(string)"} +{"name":"json_object","args":[{"type":"string"},{"type":"anyelement"}],"returns":"json"} +{"name":"json_query","args":[{"type":"json"},{"type":"string"}],"returns":"json","nullable":true} +{"name":"json_query","args":[{"type":"string"},{"type":"string"}],"returns":"string","nullable":true} +{"name":"json_query_array","args":[{"type":"json"}],"returns":"array(json)","nullable":true} +{"name":"json_query_array","args":[{"type":"json"},{"type":"string"}],"returns":"array(json)","nullable":true} +{"name":"json_remove","args":[{"type":"json"},{"type":"string"}],"returns":"json"} +{"name":"json_set","args":[{"type":"json"},{"type":"string"},{"type":"anyelement"}],"returns":"json"} +{"name":"json_strip_nulls","args":[{"type":"json"}],"returns":"json"} +{"name":"json_type","args":[{"type":"json"}],"returns":"string"} +{"name":"json_value","args":[{"type":"json"},{"type":"string"}],"returns":"string","nullable":true} +{"name":"json_value","args":[{"type":"string"},{"type":"string"}],"returns":"string","nullable":true} +{"name":"json_value_array","args":[{"type":"json"}],"returns":"array(string)","nullable":true} +{"name":"json_value_array","args":[{"type":"json"},{"type":"string"}],"returns":"array(string)","nullable":true} +{"name":"justify_days","args":[{"type":"interval"}],"returns":"interval"} +{"name":"justify_hours","args":[{"type":"interval"}],"returns":"interval"} +{"name":"justify_interval","args":[{"type":"interval"}],"returns":"interval"} +{"name":"lax_bool","args":[{"type":"json"}],"returns":"bool","nullable":true} +{"name":"lax_float64","args":[{"type":"json"}],"returns":"float64","nullable":true} +{"name":"lax_int64","args":[{"type":"json"}],"returns":"int64","nullable":true} +{"name":"lax_string","args":[{"type":"json"}],"returns":"string","nullable":true} +{"name":"length","args":[{"type":"string"}],"returns":"int64"} +{"name":"length","args":[{"type":"bytes"}],"returns":"int64"} +{"name":"ln","args":[{"type":"float64"}],"returns":"float64"} +{"name":"ln","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"log","args":[{"type":"float64"}],"returns":"float64"} +{"name":"log","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"log","args":[{"type":"float64"},{"type":"float64"}],"returns":"float64"} +{"name":"log10","args":[{"type":"float64"}],"returns":"float64"} +{"name":"log10","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"logical_and","kind":"a","args":[{"type":"bool"}],"returns":"bool","nullable":true} +{"name":"logical_or","kind":"a","args":[{"type":"bool"}],"returns":"bool","nullable":true} +{"name":"lower","args":[{"type":"string"}],"returns":"string"} +{"name":"lower","args":[{"type":"bytes"}],"returns":"bytes"} +{"name":"lpad","args":[{"type":"string"},{"type":"int64"}],"returns":"string"} +{"name":"lpad","args":[{"type":"string"},{"type":"int64"},{"type":"string"}],"returns":"string"} +{"name":"lpad","args":[{"type":"bytes"},{"type":"int64"},{"type":"bytes"}],"returns":"bytes"} +{"name":"ltrim","args":[{"type":"string"}],"returns":"string"} +{"name":"ltrim","args":[{"type":"string"},{"type":"string"}],"returns":"string"} +{"name":"ltrim","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"bytes"} +{"name":"make_interval","args":[{"type":"int64"},{"type":"int64"},{"type":"int64"}],"returns":"interval"} +{"name":"max","kind":"a","args":[{"type":"anyelement"}],"returns":"$1","nullable":true} +{"name":"md5","args":[{"type":"string"}],"returns":"bytes"} +{"name":"md5","args":[{"type":"bytes"}],"returns":"bytes"} +{"name":"min","kind":"a","args":[{"type":"anyelement"}],"returns":"$1","nullable":true} +{"name":"mod","args":[{"type":"int64"},{"type":"int64"}],"returns":"int64"} +{"name":"mod","args":[{"type":"numeric"},{"type":"numeric"}],"returns":"numeric"} +{"name":"new_uuid","returns":"uuid","never_null":true} +{"name":"normalize","args":[{"type":"string"}],"returns":"string"} +{"name":"normalize_and_casefold","args":[{"type":"string"}],"returns":"string"} +{"name":"parse_date","args":[{"type":"string"},{"type":"string"}],"returns":"date"} +{"name":"parse_json","args":[{"type":"string"}],"returns":"json"} +{"name":"parse_timestamp","args":[{"type":"string"},{"type":"string"}],"returns":"timestamp"} +{"name":"parse_timestamp","args":[{"type":"string"},{"type":"string"},{"type":"string"}],"returns":"timestamp"} +{"name":"pending_commit_timestamp","returns":"timestamp","never_null":true} +{"name":"pow","args":[{"type":"float64"},{"type":"float64"}],"returns":"float64"} +{"name":"pow","args":[{"type":"numeric"},{"type":"numeric"}],"returns":"numeric"} +{"name":"power","args":[{"type":"float64"},{"type":"float64"}],"returns":"float64"} +{"name":"power","args":[{"type":"numeric"},{"type":"numeric"}],"returns":"numeric"} +{"name":"regexp_contains","args":[{"type":"string"},{"type":"string"}],"returns":"bool"} +{"name":"regexp_contains","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"bool"} +{"name":"regexp_extract","args":[{"type":"string"},{"type":"string"}],"returns":"string","nullable":true} +{"name":"regexp_extract","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"bytes","nullable":true} +{"name":"regexp_extract_all","args":[{"type":"string"},{"type":"string"}],"returns":"array(string)"} +{"name":"regexp_extract_all","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"array(bytes)"} +{"name":"regexp_replace","args":[{"type":"string"},{"type":"string"},{"type":"string"}],"returns":"string"} +{"name":"regexp_replace","args":[{"type":"bytes"},{"type":"bytes"},{"type":"bytes"}],"returns":"bytes"} +{"name":"repeat","args":[{"type":"string"},{"type":"int64"}],"returns":"string"} +{"name":"repeat","args":[{"type":"bytes"},{"type":"int64"}],"returns":"bytes"} +{"name":"replace","args":[{"type":"string"},{"type":"string"},{"type":"string"}],"returns":"string"} +{"name":"replace","args":[{"type":"bytes"},{"type":"bytes"},{"type":"bytes"}],"returns":"bytes"} +{"name":"reverse","args":[{"type":"string"}],"returns":"string"} +{"name":"reverse","args":[{"type":"bytes"}],"returns":"bytes"} +{"name":"round","args":[{"type":"float64"}],"returns":"float64"} +{"name":"round","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"round","args":[{"type":"float64"},{"type":"int64"}],"returns":"float64"} +{"name":"round","args":[{"type":"numeric"},{"type":"int64"}],"returns":"numeric"} +{"name":"rpad","args":[{"type":"string"},{"type":"int64"}],"returns":"string"} +{"name":"rpad","args":[{"type":"string"},{"type":"int64"},{"type":"string"}],"returns":"string"} +{"name":"rpad","args":[{"type":"bytes"},{"type":"int64"},{"type":"bytes"}],"returns":"bytes"} +{"name":"rtrim","args":[{"type":"string"}],"returns":"string"} +{"name":"rtrim","args":[{"type":"string"},{"type":"string"}],"returns":"string"} +{"name":"rtrim","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"bytes"} +{"name":"safe_add","args":[{"type":"int64"},{"type":"int64"}],"returns":"int64","nullable":true} +{"name":"safe_add","args":[{"type":"float64"},{"type":"float64"}],"returns":"float64","nullable":true} +{"name":"safe_add","args":[{"type":"numeric"},{"type":"numeric"}],"returns":"numeric","nullable":true} +{"name":"safe_convert_bytes_to_string","args":[{"type":"bytes"}],"returns":"string"} +{"name":"safe_divide","args":[{"type":"int64"},{"type":"int64"}],"returns":"float64","nullable":true} +{"name":"safe_divide","args":[{"type":"float64"},{"type":"float64"}],"returns":"float64","nullable":true} +{"name":"safe_divide","args":[{"type":"numeric"},{"type":"numeric"}],"returns":"numeric","nullable":true} +{"name":"safe_multiply","args":[{"type":"int64"},{"type":"int64"}],"returns":"int64","nullable":true} +{"name":"safe_multiply","args":[{"type":"float64"},{"type":"float64"}],"returns":"float64","nullable":true} +{"name":"safe_multiply","args":[{"type":"numeric"},{"type":"numeric"}],"returns":"numeric","nullable":true} +{"name":"safe_negate","args":[{"type":"int64"}],"returns":"int64","nullable":true} +{"name":"safe_negate","args":[{"type":"float64"}],"returns":"float64","nullable":true} +{"name":"safe_negate","args":[{"type":"numeric"}],"returns":"numeric","nullable":true} +{"name":"safe_subtract","args":[{"type":"int64"},{"type":"int64"}],"returns":"int64","nullable":true} +{"name":"safe_subtract","args":[{"type":"float64"},{"type":"float64"}],"returns":"float64","nullable":true} +{"name":"safe_subtract","args":[{"type":"numeric"},{"type":"numeric"}],"returns":"numeric","nullable":true} +{"name":"safe_to_json","args":[{"type":"anyelement"}],"returns":"json","never_null":true} +{"name":"score","args":[{"type":"tokenlist"},{"type":"string"}],"returns":"float64"} +{"name":"score_ngrams","args":[{"type":"tokenlist"},{"type":"string"}],"returns":"float64"} +{"name":"search","args":[{"type":"tokenlist"},{"type":"string"}],"returns":"bool"} +{"name":"search_ngrams","args":[{"type":"tokenlist"},{"type":"string"}],"returns":"bool"} +{"name":"search_substring","args":[{"type":"tokenlist"},{"type":"string"}],"returns":"bool"} +{"name":"sha1","args":[{"type":"string"}],"returns":"bytes"} +{"name":"sha1","args":[{"type":"bytes"}],"returns":"bytes"} +{"name":"sha256","args":[{"type":"string"}],"returns":"bytes"} +{"name":"sha256","args":[{"type":"bytes"}],"returns":"bytes"} +{"name":"sha512","args":[{"type":"string"}],"returns":"bytes"} +{"name":"sha512","args":[{"type":"bytes"}],"returns":"bytes"} +{"name":"sign","args":[{"type":"int64"}],"returns":"int64"} +{"name":"sign","args":[{"type":"float64"}],"returns":"float64"} +{"name":"sign","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"sin","args":[{"type":"float64"}],"returns":"float64"} +{"name":"snippet","args":[{"type":"string"},{"type":"string"}],"returns":"json"} +{"name":"soundex","args":[{"type":"string"}],"returns":"string"} +{"name":"split","args":[{"type":"string"}],"returns":"array(string)"} +{"name":"split","args":[{"type":"string"},{"type":"string"}],"returns":"array(string)"} +{"name":"split","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"array(bytes)"} +{"name":"split_substr","args":[{"type":"string"},{"type":"string"},{"type":"int64"}],"returns":"string"} +{"name":"split_substr","args":[{"type":"string"},{"type":"string"},{"type":"int64"},{"type":"int64"}],"returns":"string"} +{"name":"sqrt","args":[{"type":"float64"}],"returns":"float64"} +{"name":"sqrt","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"starts_with","args":[{"type":"string"},{"type":"string"}],"returns":"bool"} +{"name":"starts_with","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"bool"} +{"name":"stddev","kind":"a","args":[{"type":"float64"}],"returns":"float64","nullable":true} +{"name":"stddev_samp","kind":"a","args":[{"type":"float64"}],"returns":"float64","nullable":true} +{"name":"string","args":[{"type":"timestamp"}],"returns":"string"} +{"name":"string","args":[{"type":"json"}],"returns":"string"} +{"name":"string","args":[{"type":"timestamp"},{"type":"string"}],"returns":"string"} +{"name":"string_agg","kind":"a","args":[{"type":"string"}],"returns":"string","nullable":true} +{"name":"string_agg","kind":"a","args":[{"type":"bytes"}],"returns":"bytes","nullable":true} +{"name":"string_agg","kind":"a","args":[{"type":"string"},{"type":"string"}],"returns":"string","nullable":true} +{"name":"string_agg","kind":"a","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"bytes","nullable":true} +{"name":"strpos","args":[{"type":"string"},{"type":"string"}],"returns":"int64"} +{"name":"strpos","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"int64"} +{"name":"substr","args":[{"type":"string"},{"type":"int64"}],"returns":"string"} +{"name":"substr","args":[{"type":"bytes"},{"type":"int64"}],"returns":"bytes"} +{"name":"substr","args":[{"type":"string"},{"type":"int64"},{"type":"int64"}],"returns":"string"} +{"name":"substr","args":[{"type":"bytes"},{"type":"int64"},{"type":"int64"}],"returns":"bytes"} +{"name":"substring","args":[{"type":"string"},{"type":"int64"}],"returns":"string"} +{"name":"substring","args":[{"type":"string"},{"type":"int64"},{"type":"int64"}],"returns":"string"} +{"name":"sum","kind":"a","args":[{"type":"int64"}],"returns":"int64","nullable":true} +{"name":"sum","kind":"a","args":[{"type":"float64"}],"returns":"float64","nullable":true} +{"name":"sum","kind":"a","args":[{"type":"float32"}],"returns":"float64","nullable":true} +{"name":"sum","kind":"a","args":[{"type":"numeric"}],"returns":"numeric","nullable":true} +{"name":"sum","kind":"a","args":[{"type":"interval"}],"returns":"interval","nullable":true} +{"name":"tan","args":[{"type":"float64"}],"returns":"float64"} +{"name":"timestamp","args":[{"type":"string"}],"returns":"timestamp"} +{"name":"timestamp","args":[{"type":"date"}],"returns":"timestamp"} +{"name":"timestamp","args":[{"type":"string"},{"type":"string"}],"returns":"timestamp"} +{"name":"timestamp","args":[{"type":"date"},{"type":"string"}],"returns":"timestamp"} +{"name":"timestamp_add","args":[{"type":"timestamp"},{"type":"interval"}],"returns":"timestamp"} +{"name":"timestamp_diff","args":[{"type":"timestamp"},{"type":"timestamp"},{"type":"string"}],"returns":"int64"} +{"name":"timestamp_micros","args":[{"type":"int64"}],"returns":"timestamp"} +{"name":"timestamp_millis","args":[{"type":"int64"}],"returns":"timestamp"} +{"name":"timestamp_seconds","args":[{"type":"int64"}],"returns":"timestamp"} +{"name":"timestamp_sub","args":[{"type":"timestamp"},{"type":"interval"}],"returns":"timestamp"} +{"name":"timestamp_trunc","args":[{"type":"timestamp"},{"type":"string"}],"returns":"timestamp"} +{"name":"timestamp_trunc","args":[{"type":"timestamp"},{"type":"string"},{"type":"string"}],"returns":"timestamp"} +{"name":"to_base32","args":[{"type":"bytes"}],"returns":"string"} +{"name":"to_base64","args":[{"type":"bytes"}],"returns":"string"} +{"name":"to_code_points","args":[{"type":"string"}],"returns":"array(int64)"} +{"name":"to_code_points","args":[{"type":"bytes"}],"returns":"array(int64)"} +{"name":"to_hex","args":[{"type":"bytes"}],"returns":"string"} +{"name":"to_json","args":[{"type":"anyelement"}],"returns":"json","never_null":true} +{"name":"to_json_string","args":[{"type":"json"}],"returns":"string","never_null":true} +{"name":"trim","args":[{"type":"string"}],"returns":"string"} +{"name":"trim","args":[{"type":"string"},{"type":"string"}],"returns":"string"} +{"name":"trim","args":[{"type":"bytes"},{"type":"bytes"}],"returns":"bytes"} +{"name":"trunc","args":[{"type":"float64"}],"returns":"float64"} +{"name":"trunc","args":[{"type":"numeric"}],"returns":"numeric"} +{"name":"trunc","args":[{"type":"float64"},{"type":"int64"}],"returns":"float64"} +{"name":"trunc","args":[{"type":"numeric"},{"type":"int64"}],"returns":"numeric"} +{"name":"unix_date","args":[{"type":"date"}],"returns":"int64"} +{"name":"unix_micros","args":[{"type":"timestamp"}],"returns":"int64"} +{"name":"unix_millis","args":[{"type":"timestamp"}],"returns":"int64"} +{"name":"unix_seconds","args":[{"type":"timestamp"}],"returns":"int64"} +{"name":"unnest","args":[{"type":"anyarray"}],"returns":"anyelement","nullable":true} +{"name":"upper","args":[{"type":"string"}],"returns":"string"} +{"name":"upper","args":[{"type":"bytes"}],"returns":"bytes"} +{"name":"var_samp","kind":"a","args":[{"type":"float64"}],"returns":"float64","nullable":true} +{"name":"variance","kind":"a","args":[{"type":"float64"}],"returns":"float64","nullable":true} diff --git a/internal/engine/googlesql/dialect/operators.jsonl b/internal/engine/googlesql/dialect/operators.jsonl index cbf84bbe8d..7a414883bc 100644 --- a/internal/engine/googlesql/dialect/operators.jsonl +++ b/internal/engine/googlesql/dialect/operators.jsonl @@ -1,83 +1,83 @@ -{"name": "+", "left": "int64", "right": "int64", "result": "int64"} -{"name": "+", "left": "int64", "right": "float64", "result": "float64"} -{"name": "+", "left": "int64", "right": "float32", "result": "float64"} -{"name": "+", "left": "int64", "right": "numeric", "result": "numeric"} -{"name": "+", "left": "float64", "right": "int64", "result": "float64"} -{"name": "+", "left": "float64", "right": "float64", "result": "float64"} -{"name": "+", "left": "float64", "right": "float32", "result": "float64"} -{"name": "+", "left": "float64", "right": "numeric", "result": "float64"} -{"name": "+", "left": "float32", "right": "int64", "result": "float64"} -{"name": "+", "left": "float32", "right": "float64", "result": "float64"} -{"name": "+", "left": "float32", "right": "float32", "result": "float64"} -{"name": "+", "left": "float32", "right": "numeric", "result": "float64"} -{"name": "+", "left": "numeric", "right": "int64", "result": "numeric"} -{"name": "+", "left": "numeric", "right": "float64", "result": "float64"} -{"name": "+", "left": "numeric", "right": "float32", "result": "float64"} -{"name": "+", "left": "numeric", "right": "numeric", "result": "numeric"} -{"name": "-", "left": "int64", "right": "int64", "result": "int64"} -{"name": "-", "left": "int64", "right": "float64", "result": "float64"} -{"name": "-", "left": "int64", "right": "float32", "result": "float64"} -{"name": "-", "left": "int64", "right": "numeric", "result": "numeric"} -{"name": "-", "left": "float64", "right": "int64", "result": "float64"} -{"name": "-", "left": "float64", "right": "float64", "result": "float64"} -{"name": "-", "left": "float64", "right": "float32", "result": "float64"} -{"name": "-", "left": "float64", "right": "numeric", "result": "float64"} -{"name": "-", "left": "float32", "right": "int64", "result": "float64"} -{"name": "-", "left": "float32", "right": "float64", "result": "float64"} -{"name": "-", "left": "float32", "right": "float32", "result": "float64"} -{"name": "-", "left": "float32", "right": "numeric", "result": "float64"} -{"name": "-", "left": "numeric", "right": "int64", "result": "numeric"} -{"name": "-", "left": "numeric", "right": "float64", "result": "float64"} -{"name": "-", "left": "numeric", "right": "float32", "result": "float64"} -{"name": "-", "left": "numeric", "right": "numeric", "result": "numeric"} -{"name": "*", "left": "int64", "right": "int64", "result": "int64"} -{"name": "*", "left": "int64", "right": "float64", "result": "float64"} -{"name": "*", "left": "int64", "right": "float32", "result": "float64"} -{"name": "*", "left": "int64", "right": "numeric", "result": "numeric"} -{"name": "*", "left": "float64", "right": "int64", "result": "float64"} -{"name": "*", "left": "float64", "right": "float64", "result": "float64"} -{"name": "*", "left": "float64", "right": "float32", "result": "float64"} -{"name": "*", "left": "float64", "right": "numeric", "result": "float64"} -{"name": "*", "left": "float32", "right": "int64", "result": "float64"} -{"name": "*", "left": "float32", "right": "float64", "result": "float64"} -{"name": "*", "left": "float32", "right": "float32", "result": "float64"} -{"name": "*", "left": "float32", "right": "numeric", "result": "float64"} -{"name": "*", "left": "numeric", "right": "int64", "result": "numeric"} -{"name": "*", "left": "numeric", "right": "float64", "result": "float64"} -{"name": "*", "left": "numeric", "right": "float32", "result": "float64"} -{"name": "*", "left": "numeric", "right": "numeric", "result": "numeric"} -{"name": "/", "left": "int64", "right": "int64", "result": "float64"} -{"name": "/", "left": "int64", "right": "float64", "result": "float64"} -{"name": "/", "left": "int64", "right": "float32", "result": "float64"} -{"name": "/", "left": "int64", "right": "numeric", "result": "numeric"} -{"name": "/", "left": "float64", "right": "int64", "result": "float64"} -{"name": "/", "left": "float64", "right": "float64", "result": "float64"} -{"name": "/", "left": "float64", "right": "float32", "result": "float64"} -{"name": "/", "left": "float64", "right": "numeric", "result": "float64"} -{"name": "/", "left": "float32", "right": "int64", "result": "float64"} -{"name": "/", "left": "float32", "right": "float64", "result": "float64"} -{"name": "/", "left": "float32", "right": "float32", "result": "float64"} -{"name": "/", "left": "float32", "right": "numeric", "result": "float64"} -{"name": "/", "left": "numeric", "right": "int64", "result": "numeric"} -{"name": "/", "left": "numeric", "right": "float64", "result": "float64"} -{"name": "/", "left": "numeric", "right": "float32", "result": "float64"} -{"name": "/", "left": "numeric", "right": "numeric", "result": "numeric"} -{"name": "||", "left": "string", "right": "string", "result": "string"} -{"name": "||", "left": "bytes", "right": "bytes", "result": "bytes"} -{"name": "+", "left": "timestamp", "right": "interval", "result": "timestamp"} -{"name": "-", "left": "timestamp", "right": "interval", "result": "timestamp"} -{"name": "+", "left": "interval", "right": "timestamp", "result": "timestamp"} -{"name": "-", "left": "date", "right": "date", "result": "interval"} -{"name": "-", "left": "timestamp", "right": "timestamp", "result": "interval"} -{"name": "+", "left": "interval", "right": "interval", "result": "interval"} -{"name": "-", "left": "interval", "right": "interval", "result": "interval"} -{"name": "*", "left": "interval", "right": "int64", "result": "interval"} -{"name": "/", "left": "interval", "right": "int64", "result": "interval"} -{"name": "&", "left": "int64", "right": "int64", "result": "int64"} -{"name": "|", "left": "int64", "right": "int64", "result": "int64"} -{"name": "^", "left": "int64", "right": "int64", "result": "int64"} -{"name": "<<", "left": "int64", "right": "int64", "result": "int64"} -{"name": ">>", "left": "int64", "right": "int64", "result": "int64"} -{"name": "&", "left": "bytes", "right": "bytes", "result": "bytes"} -{"name": "|", "left": "bytes", "right": "bytes", "result": "bytes"} -{"name": "^", "left": "bytes", "right": "bytes", "result": "bytes"} +{"name":"+","left":"int64","right":"int64","result":"int64"} +{"name":"+","left":"int64","right":"float64","result":"float64"} +{"name":"+","left":"int64","right":"float32","result":"float64"} +{"name":"+","left":"int64","right":"numeric","result":"numeric"} +{"name":"+","left":"float64","right":"int64","result":"float64"} +{"name":"+","left":"float64","right":"float64","result":"float64"} +{"name":"+","left":"float64","right":"float32","result":"float64"} +{"name":"+","left":"float64","right":"numeric","result":"float64"} +{"name":"+","left":"float32","right":"int64","result":"float64"} +{"name":"+","left":"float32","right":"float64","result":"float64"} +{"name":"+","left":"float32","right":"float32","result":"float64"} +{"name":"+","left":"float32","right":"numeric","result":"float64"} +{"name":"+","left":"numeric","right":"int64","result":"numeric"} +{"name":"+","left":"numeric","right":"float64","result":"float64"} +{"name":"+","left":"numeric","right":"float32","result":"float64"} +{"name":"+","left":"numeric","right":"numeric","result":"numeric"} +{"name":"-","left":"int64","right":"int64","result":"int64"} +{"name":"-","left":"int64","right":"float64","result":"float64"} +{"name":"-","left":"int64","right":"float32","result":"float64"} +{"name":"-","left":"int64","right":"numeric","result":"numeric"} +{"name":"-","left":"float64","right":"int64","result":"float64"} +{"name":"-","left":"float64","right":"float64","result":"float64"} +{"name":"-","left":"float64","right":"float32","result":"float64"} +{"name":"-","left":"float64","right":"numeric","result":"float64"} +{"name":"-","left":"float32","right":"int64","result":"float64"} +{"name":"-","left":"float32","right":"float64","result":"float64"} +{"name":"-","left":"float32","right":"float32","result":"float64"} +{"name":"-","left":"float32","right":"numeric","result":"float64"} +{"name":"-","left":"numeric","right":"int64","result":"numeric"} +{"name":"-","left":"numeric","right":"float64","result":"float64"} +{"name":"-","left":"numeric","right":"float32","result":"float64"} +{"name":"-","left":"numeric","right":"numeric","result":"numeric"} +{"name":"*","left":"int64","right":"int64","result":"int64"} +{"name":"*","left":"int64","right":"float64","result":"float64"} +{"name":"*","left":"int64","right":"float32","result":"float64"} +{"name":"*","left":"int64","right":"numeric","result":"numeric"} +{"name":"*","left":"float64","right":"int64","result":"float64"} +{"name":"*","left":"float64","right":"float64","result":"float64"} +{"name":"*","left":"float64","right":"float32","result":"float64"} +{"name":"*","left":"float64","right":"numeric","result":"float64"} +{"name":"*","left":"float32","right":"int64","result":"float64"} +{"name":"*","left":"float32","right":"float64","result":"float64"} +{"name":"*","left":"float32","right":"float32","result":"float64"} +{"name":"*","left":"float32","right":"numeric","result":"float64"} +{"name":"*","left":"numeric","right":"int64","result":"numeric"} +{"name":"*","left":"numeric","right":"float64","result":"float64"} +{"name":"*","left":"numeric","right":"float32","result":"float64"} +{"name":"*","left":"numeric","right":"numeric","result":"numeric"} +{"name":"/","left":"int64","right":"int64","result":"float64"} +{"name":"/","left":"int64","right":"float64","result":"float64"} +{"name":"/","left":"int64","right":"float32","result":"float64"} +{"name":"/","left":"int64","right":"numeric","result":"numeric"} +{"name":"/","left":"float64","right":"int64","result":"float64"} +{"name":"/","left":"float64","right":"float64","result":"float64"} +{"name":"/","left":"float64","right":"float32","result":"float64"} +{"name":"/","left":"float64","right":"numeric","result":"float64"} +{"name":"/","left":"float32","right":"int64","result":"float64"} +{"name":"/","left":"float32","right":"float64","result":"float64"} +{"name":"/","left":"float32","right":"float32","result":"float64"} +{"name":"/","left":"float32","right":"numeric","result":"float64"} +{"name":"/","left":"numeric","right":"int64","result":"numeric"} +{"name":"/","left":"numeric","right":"float64","result":"float64"} +{"name":"/","left":"numeric","right":"float32","result":"float64"} +{"name":"/","left":"numeric","right":"numeric","result":"numeric"} +{"name":"||","left":"string","right":"string","result":"string"} +{"name":"||","left":"bytes","right":"bytes","result":"bytes"} +{"name":"+","left":"timestamp","right":"interval","result":"timestamp"} +{"name":"+","left":"interval","right":"interval","result":"interval"} +{"name":"-","left":"timestamp","right":"interval","result":"timestamp"} +{"name":"-","left":"interval","right":"interval","result":"interval"} +{"name":"+","left":"interval","right":"timestamp","result":"timestamp"} +{"name":"-","left":"date","right":"date","result":"interval"} +{"name":"-","left":"timestamp","right":"timestamp","result":"interval"} +{"name":"*","left":"interval","right":"int64","result":"interval"} +{"name":"/","left":"interval","right":"int64","result":"interval"} +{"name":"\u0026","left":"int64","right":"int64","result":"int64"} +{"name":"|","left":"int64","right":"int64","result":"int64"} +{"name":"^","left":"int64","right":"int64","result":"int64"} +{"name":"\u003c\u003c","left":"int64","right":"int64","result":"int64"} +{"name":"\u003e\u003e","left":"int64","right":"int64","result":"int64"} +{"name":"\u0026","left":"bytes","right":"bytes","result":"bytes"} +{"name":"|","left":"bytes","right":"bytes","result":"bytes"} +{"name":"^","left":"bytes","right":"bytes","result":"bytes"} diff --git a/internal/engine/googlesql/dialect/types.jsonl b/internal/engine/googlesql/dialect/types.jsonl index 8c6dac4005..10f671d9a7 100644 --- a/internal/engine/googlesql/dialect/types.jsonl +++ b/internal/engine/googlesql/dialect/types.jsonl @@ -1,23 +1,23 @@ -{"name": "bool", "category": "B"} -{"name": "int64", "category": "N"} -{"name": "float32", "category": "N"} -{"name": "float64", "category": "N"} -{"name": "numeric", "category": "N"} -{"name": "bignumeric", "category": "N"} -{"name": "string", "category": "S"} -{"name": "bytes", "category": "S"} -{"name": "uuid", "category": "S"} -{"name": "date", "category": "D"} -{"name": "datetime", "category": "D"} -{"name": "time", "category": "D"} -{"name": "timestamp", "category": "D"} -{"name": "interval", "category": "T"} -{"name": "json", "category": "U"} -{"name": "geography", "category": "U"} -{"name": "struct", "category": "U"} -{"name": "enum", "category": "U"} -{"name": "proto", "category": "U"} -{"name": "tokenlist", "category": "U"} -{"name": "array", "category": "A"} -{"name": "range", "category": "U"} -{"name": "map", "category": "U"} +{"name":"bool","category":"B"} +{"name":"int64","category":"N"} +{"name":"float32","category":"N"} +{"name":"float64","category":"N"} +{"name":"numeric","category":"N"} +{"name":"bignumeric","category":"N"} +{"name":"string","category":"S"} +{"name":"bytes","category":"S"} +{"name":"uuid","category":"S"} +{"name":"date","category":"D"} +{"name":"datetime","category":"D"} +{"name":"time","category":"D"} +{"name":"timestamp","category":"D"} +{"name":"interval","category":"T"} +{"name":"json","category":"U"} +{"name":"geography","category":"U"} +{"name":"struct","category":"U"} +{"name":"enum","category":"U"} +{"name":"proto","category":"U"} +{"name":"tokenlist","category":"U"} +{"name":"array","category":"A"} +{"name":"range","category":"U"} +{"name":"map","category":"U"} diff --git a/internal/goldeneye/README.md b/internal/goldeneye/README.md index e4e8374fff..ed2c67d040 100644 --- a/internal/goldeneye/README.md +++ b/internal/goldeneye/README.md @@ -110,9 +110,15 @@ the hand-written files alone, and the checks do not look at them. the gRPC endpoint such as `localhost:15000`, reached without TLS or credentials as Omni is, and writes into `internal/engine/googlesql/dialect`, since sqlc's engine is named after the language Spanner speaks. Spanner - keeps no catalog of its types, functions or operators, so `types.jsonl`, - `functions.jsonl` and `operators.jsonl` are hand-written. What it does - describe is its information schema: `relations.jsonl` is every view of + keeps no catalog of its types, functions or operators, so they are read + by asking: `spanner/library.jsonl` lists the function signatures to try, + and each is compiled in PLAN mode against a probe database, the ones + Spanner accepts written to `functions.jsonl` with the type it reports; + `operators.jsonl` is every binary operator Spanner accepts over the + operands tried, and `types.jsonl` the types the dialect lists, each one + Spanner has checked by a cast. Whether a result can be NULL is what + Spanner does not say, so `library.jsonl` says it. Spanner also + describes its information schema: `relations.jsonl` is every view of `INFORMATION_SCHEMA` and `SPANNER_SYS`, read from `INFORMATION_SCHEMA` itself in a database created for the purpose in the instance Omni's single server provides, `projects/default/instances/default`. Names are diff --git a/internal/goldeneye/dialect/dialect.go b/internal/goldeneye/dialect/dialect.go index 6b053a120a..59a530cd47 100644 --- a/internal/goldeneye/dialect/dialect.go +++ b/internal/goldeneye/dialect/dialect.go @@ -69,6 +69,9 @@ type Function struct { Args []Arg `json:"args,omitempty"` Returns string `json:"returns"` Nullable bool `json:"nullable,omitempty"` + // NeverNull marks a result that is never NULL even when an argument + // is, in a dialect that propagates nullability. + NeverNull bool `json:"never_null,omitempty"` } // Arg is one of a function's parameters. Mode is 'i'n, 'o'ut, 'b'oth, diff --git a/internal/goldeneye/spanner/library.go b/internal/goldeneye/spanner/library.go new file mode 100644 index 0000000000..6aaadc5d4a --- /dev/null +++ b/internal/goldeneye/spanner/library.go @@ -0,0 +1,428 @@ +package spanner + +import ( + "bufio" + "bytes" + "context" + _ "embed" + "encoding/json" + "fmt" + "sort" + "strconv" + "strings" + + "cloud.google.com/go/spanner/apiv1/spannerpb" + + "github.com/sqlc-dev/sqlc/internal/goldeneye/dialect" +) + +// Spanner keeps no catalog of its types, functions or operators, so the +// library is read by asking: each candidate is compiled in PLAN mode, which +// runs nothing, against a probe database, and what Spanner accepts is kept +// with the type it reports. + +// libraryDatabase is the probe database the library is read from. +const libraryDatabase = "goldeneye_library" + +// librarySchema is the probe database: a table with a NOT NULL column of +// each type a candidate's argument can be, the search indexes SCORE and +// SEARCH need, the sequence GET_NEXT_SEQUENCE_VALUE draws from, and a +// commit timestamp column for PENDING_COMMIT_TIMESTAMP. +var librarySchema = []string{ + `CREATE TABLE probe ( + id INT64 NOT NULL, + i INT64 NOT NULL, + s STRING(MAX) NOT NULL, + f FLOAT64 NOT NULL, + f32 FLOAT32 NOT NULL, + n NUMERIC NOT NULL, + b BOOL NOT NULL, + byt BYTES(MAX) NOT NULL, + d DATE NOT NULL, + ts TIMESTAMP NOT NULL, + j JSON NOT NULL, + ai ARRAY NOT NULL, + ast ARRAY NOT NULL, + af ARRAY NOT NULL, + af32 ARRAY NOT NULL, + aj ARRAY NOT NULL, + ab ARRAY NOT NULL, + tl TOKENLIST AS (TOKENIZE_FULLTEXT(s)) HIDDEN, + tl_substring TOKENLIST AS (TOKENIZE_SUBSTRING(s)) HIDDEN, + tl_ngrams TOKENLIST AS (TOKENIZE_NGRAMS(s)) HIDDEN, +) PRIMARY KEY (id)`, + `CREATE SEARCH INDEX probe_search ON probe(tl, tl_substring, tl_ngrams)`, + `CREATE SEQUENCE probe_sequence OPTIONS (sequence_kind = 'bit_reversed_positive')`, + `CREATE TABLE probe_commits ( + id INT64 NOT NULL, + committed_at TIMESTAMP OPTIONS (allow_commit_timestamp = true), +) PRIMARY KEY (id)`, +} + +// probeValues is the expression a candidate's argument of each type is +// called with: the probe table's column of that type, so that the call is +// made the way a query makes it, on a value rather than a constant. +var probeValues = map[string]string{ + "int64": "i", + "string": "s", + "float64": "f", + "float32": "f32", + "numeric": "n", + "bool": "b", + "bytes": "byt", + "date": "d", + "timestamp": "ts", + "json": "j", + "interval": "INTERVAL 1 DAY", + "tokenlist": "tl", + "array(int64)": "ai", + "array(string)": "ast", + "array(float64)": "af", + "array(float32)": "af32", + "array(json)": "aj", + "array(bytes)": "ab", +} + +// libraryTypes are the types the dialect lists, in the order types.jsonl +// does, with the category sqlc groups each in and the spelling Spanner is +// asked to cast to. A type Spanner does not have, BigQuery's BIGNUMERIC or +// DATETIME, is GoogleSQL's all the same, and the engine is GoogleSQL's, +// shared with BigQuery; those have no probe and are listed as they are. +var libraryTypes = []struct { + dialect.Type + probe string +}{ + {dialect.Type{Name: "bool", Category: "B"}, "BOOL"}, + {dialect.Type{Name: "int64", Category: "N"}, "INT64"}, + {dialect.Type{Name: "float32", Category: "N"}, "FLOAT32"}, + {dialect.Type{Name: "float64", Category: "N"}, "FLOAT64"}, + {dialect.Type{Name: "numeric", Category: "N"}, "NUMERIC"}, + {dialect.Type{Name: "bignumeric", Category: "N"}, ""}, + {dialect.Type{Name: "string", Category: "S"}, "STRING"}, + {dialect.Type{Name: "bytes", Category: "S"}, "BYTES"}, + {dialect.Type{Name: "uuid", Category: "S"}, "UUID"}, + {dialect.Type{Name: "date", Category: "D"}, "DATE"}, + {dialect.Type{Name: "datetime", Category: "D"}, ""}, + {dialect.Type{Name: "time", Category: "D"}, ""}, + {dialect.Type{Name: "timestamp", Category: "D"}, "TIMESTAMP"}, + {dialect.Type{Name: "interval", Category: "T"}, "INTERVAL"}, + {dialect.Type{Name: "json", Category: "U"}, "JSON"}, + {dialect.Type{Name: "geography", Category: "U"}, ""}, + {dialect.Type{Name: "struct", Category: "U"}, "STRUCT"}, + {dialect.Type{Name: "enum", Category: "U"}, ""}, + {dialect.Type{Name: "proto", Category: "U"}, ""}, + {dialect.Type{Name: "tokenlist", Category: "U"}, ""}, + {dialect.Type{Name: "array", Category: "A"}, "ARRAY"}, + {dialect.Type{Name: "range", Category: "U"}, ""}, + {dialect.Type{Name: "map", Category: "U"}, ""}, +} + +//go:embed library.jsonl +var libraryCandidates []byte + +// candidate is one line of library.jsonl. +type candidate struct { + Name string `json:"name"` + Kind string `json:"kind,omitempty"` + Args []string `json:"args,omitempty"` + Returns string `json:"returns,omitempty"` + Nullable bool `json:"nullable,omitempty"` + NeverNull bool `json:"never_null,omitempty"` + // Expr is the expression to compile in place of name(args), and Query + // the whole statement, with $1, $2... standing for the arguments. + Expr string `json:"expr,omitempty"` + Query string `json:"query,omitempty"` +} + +// polymorphic reports whether a candidate takes a T or an AT. +func (c candidate) polymorphic() bool { + for _, a := range c.Args { + if a == "T" || a == "AT" { + return true + } + } + return false +} + +// sql is the statement that compiles a call of the candidate with the +// arguments given, as types. +func (c candidate) sql(args []string) (string, error) { + vals := make([]string, len(args)) + for i, a := range args { + if typ, lit, ok := strings.Cut(a, "="); ok { + _ = typ + vals[i] = lit + continue + } + v, ok := probeValues[a] + if !ok { + if c.Expr == "" && c.Query == "" { + return "", fmt.Errorf("%s: no probe value for %s", c.Name, a) + } + v = "NULL" + } + vals[i] = v + } + fill := func(tmpl string) string { + for i := len(vals); i >= 1; i-- { + tmpl = strings.ReplaceAll(tmpl, "$"+strconv.Itoa(i), vals[i-1]) + } + return tmpl + } + if c.Query != "" { + return fill(c.Query), nil + } + call := c.Name + "(" + strings.Join(vals, ", ") + ")" + if c.Expr != "" { + call = fill(c.Expr) + } + return "SELECT " + call + " AS r FROM probe", nil +} + +// seedType is the type an argument is written as in functions.jsonl. +func seedType(arg string) string { + switch arg { + case "T": + return "anyelement" + case "AT": + return "anyarray" + } + typ, _, _ := strings.Cut(arg, "=") + return typ +} + +// readLibrary reads types.jsonl, functions.jsonl and operators.jsonl from +// the probe database. +func readLibrary(ctx context.Context, s *server, session string) (dialect.Files, error) { + compile := func(sql string) (*spannerpb.Type, error) { + req := &spannerpb.ExecuteSqlRequest{Session: session, Sql: sql, QueryMode: spannerpb.ExecuteSqlRequest_PLAN} + if isDML(sql) { + req.Transaction = &spannerpb.TransactionSelector{Selector: &spannerpb.TransactionSelector_Begin{ + Begin: &spannerpb.TransactionOptions{Mode: &spannerpb.TransactionOptions_ReadWrite_{ReadWrite: &spannerpb.TransactionOptions_ReadWrite{}}}, + }} + req.Seqno = 1 + } + rs, err := s.data.ExecuteSql(ctx, req) + if err != nil { + return nil, err + } + if rs.Metadata != nil && rs.Metadata.Transaction != nil && len(rs.Metadata.Transaction.Id) > 0 { + s.data.Rollback(context.WithoutCancel(ctx), &spannerpb.RollbackRequest{Session: session, TransactionId: rs.Metadata.Transaction.Id}) + } + if rs.Metadata == nil || rs.Metadata.RowType == nil || len(rs.Metadata.RowType.Fields) == 0 { + return nil, nil + } + return rs.Metadata.RowType.Fields[0].Type, nil + } + + types, err := readLibraryTypes(compile) + if err != nil { + return nil, err + } + funcs, err := readLibraryFunctions(compile) + if err != nil { + return nil, err + } + operators, err := readLibraryOperators(compile) + if err != nil { + return nil, err + } + files := dialect.Files{} + for name, records := range map[string]any{ + dialect.TypesFile: types, + dialect.FunctionsFile: funcs, + dialect.OperatorsFile: operators, + } { + var blob []byte + var err error + switch r := records.(type) { + case []dialect.Type: + blob, err = dialect.JSONL(r) + case []dialect.Function: + blob, err = dialect.JSONL(r) + case []dialect.Operator: + blob, err = dialect.JSONL(r) + } + if err != nil { + return nil, err + } + files[name] = blob + } + return files, nil +} + +type compiler func(sql string) (*spannerpb.Type, error) + +// readLibraryTypes checks that Spanner has each type the dialect lists with +// a probe, failing when one is gone, since sqlc would then accept a schema +// Spanner rejects. +func readLibraryTypes(compile compiler) ([]dialect.Type, error) { + types := make([]dialect.Type, 0, len(libraryTypes)) + for _, t := range libraryTypes { + if t.probe != "" { + // IS NULL, since Spanner returns no STRUCT as a column. + if _, err := compile("SELECT CAST(NULL AS " + t.probe + ") IS NULL AS r"); err != nil { + return nil, fmt.Errorf("type %s: %w", t.Name, err) + } + } + types = append(types, t.Type) + } + return types, nil +} + +// readLibraryFunctions compiles every candidate in library.jsonl and lists +// the ones Spanner accepts, sorted by name and then by arity, the order the +// candidates give otherwise kept. +func readLibraryFunctions(compile compiler) ([]dialect.Function, error) { + var funcs []dialect.Function + scanner := bufio.NewScanner(bytes.NewReader(libraryCandidates)) + for line := 1; scanner.Scan(); line++ { + text := strings.TrimSpace(scanner.Text()) + if text == "" || strings.HasPrefix(text, "#") { + continue + } + var c candidate + if err := json.Unmarshal([]byte(text), &c); err != nil { + return nil, fmt.Errorf("library.jsonl:%d: %w", line, err) + } + returns, ok, err := probeCandidate(compile, c) + if err != nil { + return nil, fmt.Errorf("library.jsonl:%d: %w", line, err) + } + if !ok { + continue + } + fn := dialect.Function{Name: c.Name, Kind: c.Kind, Returns: returns, Nullable: c.Nullable, NeverNull: c.NeverNull} + for _, a := range c.Args { + fn.Args = append(fn.Args, dialect.Arg{Type: seedType(a)}) + } + funcs = append(funcs, fn) + } + if err := scanner.Err(); err != nil { + return nil, err + } + sort.SliceStable(funcs, func(i, j int) bool { + if funcs[i].Name != funcs[j].Name { + return funcs[i].Name < funcs[j].Name + } + return len(funcs[i].Args) < len(funcs[j].Args) + }) + return funcs, nil +} + +// probeCandidate compiles a candidate and returns the type it is seeded +// with, or false when Spanner rejects it. A candidate that is not there is +// left out rather than failing the run: a release of Spanner that drops a +// function shows in the diff of functions.jsonl. +func probeCandidate(compile compiler, c candidate) (string, bool, error) { + if !c.polymorphic() { + sql, err := c.sql(c.Args) + if err != nil { + return "", false, err + } + t, err := compile(sql) + if err != nil { + return "", false, nil + } + got := typeSpelling(t) + if c.Query != "" && c.Returns != "" { + // The statement's result, if it has one, is not the call's. + return c.Returns, true, nil + } + if c.Returns != "" && c.Returns != got { + return "", false, fmt.Errorf("%s returns %s, not %s", c.Name, got, c.Returns) + } + return got, true, nil + } + for _, elem := range []string{"int64", "string"} { + args := make([]string, len(c.Args)) + for i, a := range c.Args { + switch a { + case "T": + args[i] = elem + case "AT": + args[i] = "array(" + elem + ")" + default: + args[i] = a + } + } + sql, err := c.sql(args) + if err != nil { + return "", false, err + } + t, err := compile(sql) + if err != nil { + return "", false, nil + } + want := c.Returns + for i := len(args); i >= 1; i-- { + want = strings.ReplaceAll(want, "$"+strconv.Itoa(i), args[i-1]) + } + switch want { + case "anyelement": + want = elem + case "anyarray": + want = "array(" + elem + ")" + } + if got := typeSpelling(t); got != want { + return "", false, fmt.Errorf("%s(%s) returns %s, not %s", c.Name, strings.Join(args, ", "), got, want) + } + } + return c.Returns, true, nil +} + +// typeSpelling spells a type the way functions.jsonl does. +func typeSpelling(t *spannerpb.Type) string { + if t == nil { + return "" + } + if t.Code == spannerpb.TypeCode_ARRAY { + return "array(" + typeSpelling(t.ArrayElementType) + ")" + } + return strings.ToLower(t.Code.String()) +} + +// libraryOperandTypes are the operands each operator is tried with. +var ( + numericOperands = []string{"int64", "float64", "float32", "numeric"} + operatorPairs = func() [][3]string { + var pairs [][3]string + for _, op := range []string{"+", "-", "*", "/"} { + for _, l := range numericOperands { + for _, r := range numericOperands { + pairs = append(pairs, [3]string{op, l, r}) + } + } + } + for _, t := range []string{"string", "bytes"} { + pairs = append(pairs, [3]string{"||", t, t}) + } + for _, op := range []string{"+", "-"} { + pairs = append(pairs, [3]string{op, "timestamp", "interval"}, [3]string{op, "interval", "interval"}) + } + return append(pairs, + [3]string{"+", "interval", "timestamp"}, + [3]string{"-", "date", "date"}, [3]string{"-", "timestamp", "timestamp"}, + [3]string{"*", "interval", "int64"}, [3]string{"/", "interval", "int64"}, + [3]string{"&", "int64", "int64"}, [3]string{"|", "int64", "int64"}, [3]string{"^", "int64", "int64"}, + [3]string{"<<", "int64", "int64"}, [3]string{">>", "int64", "int64"}, + [3]string{"&", "bytes", "bytes"}, [3]string{"|", "bytes", "bytes"}, [3]string{"^", "bytes", "bytes"}, + ) + }() +) + +// readLibraryOperators compiles each binary operator over the operands it +// is tried with and lists the ones Spanner accepts with their result. The +// comparisons are left to dialect.json's rules, which make every one of +// them a BOOL. +func readLibraryOperators(compile compiler) ([]dialect.Operator, error) { + var ops []dialect.Operator + for _, p := range operatorPairs { + t, err := compile("SELECT " + probeValues[p[1]] + " " + p[0] + " " + probeValues[p[2]] + " AS r FROM probe") + if err != nil { + continue + } + ops = append(ops, dialect.Operator{Name: p[0], Left: p[1], Right: p[2], Result: typeSpelling(t)}) + } + return ops, nil +} diff --git a/internal/goldeneye/spanner/library.jsonl b/internal/goldeneye/spanner/library.jsonl new file mode 100644 index 0000000000..a0a9c286b9 --- /dev/null +++ b/internal/goldeneye/spanner/library.jsonl @@ -0,0 +1,322 @@ +# Candidate signatures of Spanner's GoogleSQL functions. Spanner keeps no +# catalog of its functions, so this file lists the calls to try; goldeneye +# compiles each one in PLAN mode against the probe table in library.go, +# keeps the ones Spanner accepts and writes the result type Spanner reports. +# +# An argument is a type, a type=literal for an argument Spanner wants as a +# literal, T for a polymorphic value or AT for an array of one. A +# polymorphic signature is tried with INT64 and STRING, and its returns +# ($1 for the first argument's type, array($1), anyelement for an AT's +# element) must match what Spanner reports for both. nullable and +# never_null say what Spanner cannot: whether the result can be NULL when +# the arguments are not, or is never NULL when they are. +# Aggregates: NULL over no rows, except the counts. +{"name": "count", "kind": "a", "args": ["T"], "returns": "int64", "never_null": true} +{"name": "countif", "kind": "a", "args": ["bool"], "never_null": true} +{"name": "any_value", "kind": "a", "args": ["T"], "returns": "$1", "nullable": true} +{"name": "array_agg", "kind": "a", "args": ["T"], "returns": "array($1)", "nullable": true} +{"name": "array_concat_agg", "kind": "a", "args": ["AT"], "returns": "$1", "nullable": true} +{"name": "avg", "kind": "a", "args": ["int64"], "nullable": true} +{"name": "avg", "kind": "a", "args": ["float64"], "nullable": true} +{"name": "avg", "kind": "a", "args": ["float32"], "nullable": true} +{"name": "avg", "kind": "a", "args": ["numeric"], "nullable": true} +{"name": "avg", "kind": "a", "args": ["interval"], "nullable": true} +{"name": "bit_and", "kind": "a", "args": ["int64"], "nullable": true} +{"name": "bit_or", "kind": "a", "args": ["int64"], "nullable": true} +{"name": "bit_xor", "kind": "a", "args": ["int64"], "nullable": true} +{"name": "logical_and", "kind": "a", "args": ["bool"], "nullable": true} +{"name": "logical_or", "kind": "a", "args": ["bool"], "nullable": true} +{"name": "max", "kind": "a", "args": ["T"], "returns": "$1", "nullable": true} +{"name": "min", "kind": "a", "args": ["T"], "returns": "$1", "nullable": true} +{"name": "string_agg", "kind": "a", "args": ["string"], "nullable": true} +{"name": "string_agg", "kind": "a", "args": ["string", "string=','"], "nullable": true} +{"name": "string_agg", "kind": "a", "args": ["bytes"], "nullable": true} +{"name": "string_agg", "kind": "a", "args": ["bytes", "bytes=b','"], "nullable": true} +{"name": "sum", "kind": "a", "args": ["int64"], "nullable": true} +{"name": "sum", "kind": "a", "args": ["float64"], "nullable": true} +{"name": "sum", "kind": "a", "args": ["float32"], "nullable": true} +{"name": "sum", "kind": "a", "args": ["numeric"], "nullable": true} +{"name": "sum", "kind": "a", "args": ["interval"], "nullable": true} +{"name": "stddev", "kind": "a", "args": ["float64"], "nullable": true} +{"name": "stddev_samp", "kind": "a", "args": ["float64"], "nullable": true} +{"name": "variance", "kind": "a", "args": ["float64"], "nullable": true} +{"name": "var_samp", "kind": "a", "args": ["float64"], "nullable": true} +# Window functions. +# Math. +{"name": "abs", "args": ["int64"]} +{"name": "abs", "args": ["float64"]} +{"name": "abs", "args": ["float32"]} +{"name": "abs", "args": ["numeric"]} +{"name": "sign", "args": ["int64"]} +{"name": "sign", "args": ["float64"]} +{"name": "sign", "args": ["numeric"]} +{"name": "mod", "args": ["int64", "int64"]} +{"name": "mod", "args": ["numeric", "numeric"]} +{"name": "div", "args": ["int64", "int64"]} +{"name": "div", "args": ["numeric", "numeric"]} +{"name": "round", "args": ["float64"]} +{"name": "round", "args": ["numeric"]} +{"name": "round", "args": ["float64", "int64"]} +{"name": "round", "args": ["numeric", "int64"]} +{"name": "trunc", "args": ["float64"]} +{"name": "trunc", "args": ["numeric"]} +{"name": "trunc", "args": ["float64", "int64"]} +{"name": "trunc", "args": ["numeric", "int64"]} +{"name": "ceil", "args": ["float64"]} +{"name": "ceil", "args": ["numeric"]} +{"name": "ceiling", "args": ["float64"]} +{"name": "ceiling", "args": ["numeric"]} +{"name": "floor", "args": ["float64"]} +{"name": "floor", "args": ["numeric"]} +{"name": "sqrt", "args": ["float64"]} +{"name": "sqrt", "args": ["numeric"]} +{"name": "pow", "args": ["float64", "float64"]} +{"name": "pow", "args": ["numeric", "numeric"]} +{"name": "power", "args": ["float64", "float64"]} +{"name": "power", "args": ["numeric", "numeric"]} +{"name": "exp", "args": ["float64"]} +{"name": "exp", "args": ["numeric"]} +{"name": "ln", "args": ["float64"]} +{"name": "ln", "args": ["numeric"]} +{"name": "log", "args": ["float64"]} +{"name": "log", "args": ["numeric"]} +{"name": "log", "args": ["float64", "float64"]} +{"name": "log10", "args": ["float64"]} +{"name": "log10", "args": ["numeric"]} +{"name": "ieee_divide", "args": ["float64", "float64"]} +{"name": "safe_divide", "args": ["int64", "int64"], "nullable": true} +{"name": "safe_divide", "args": ["float64", "float64"], "nullable": true} +{"name": "safe_divide", "args": ["numeric", "numeric"], "nullable": true} +{"name": "safe_add", "args": ["int64", "int64"], "nullable": true} +{"name": "safe_add", "args": ["float64", "float64"], "nullable": true} +{"name": "safe_add", "args": ["numeric", "numeric"], "nullable": true} +{"name": "safe_subtract", "args": ["int64", "int64"], "nullable": true} +{"name": "safe_subtract", "args": ["float64", "float64"], "nullable": true} +{"name": "safe_subtract", "args": ["numeric", "numeric"], "nullable": true} +{"name": "safe_multiply", "args": ["int64", "int64"], "nullable": true} +{"name": "safe_multiply", "args": ["float64", "float64"], "nullable": true} +{"name": "safe_multiply", "args": ["numeric", "numeric"], "nullable": true} +{"name": "safe_negate", "args": ["int64"], "nullable": true} +{"name": "safe_negate", "args": ["float64"], "nullable": true} +{"name": "safe_negate", "args": ["numeric"], "nullable": true} +{"name": "is_inf", "args": ["float64"]} +{"name": "is_nan", "args": ["float64"]} +{"name": "sin", "args": ["float64"]} +{"name": "cos", "args": ["float64"]} +{"name": "tan", "args": ["float64"]} +{"name": "asin", "args": ["float64"]} +{"name": "acos", "args": ["float64"]} +{"name": "atan", "args": ["float64"]} +{"name": "atan2", "args": ["float64", "float64"]} +{"name": "bit_count", "args": ["int64"]} +{"name": "bit_count", "args": ["bytes"]} +{"name": "bit_reverse", "args": ["int64", "bool"]} +# Strings. +{"name": "length", "args": ["string"]} +{"name": "length", "args": ["bytes"]} +{"name": "byte_length", "args": ["string"]} +{"name": "byte_length", "args": ["bytes"]} +{"name": "char_length", "args": ["string"]} +{"name": "character_length", "args": ["string"]} +{"name": "concat", "args": ["string", "string"]} +{"name": "concat", "args": ["bytes", "bytes"]} +{"name": "lower", "args": ["string"]} +{"name": "lower", "args": ["bytes"]} +{"name": "upper", "args": ["string"]} +{"name": "upper", "args": ["bytes"]} +{"name": "trim", "args": ["string"]} +{"name": "trim", "args": ["string", "string"]} +{"name": "trim", "args": ["bytes", "bytes"]} +{"name": "ltrim", "args": ["string"]} +{"name": "ltrim", "args": ["string", "string"]} +{"name": "rtrim", "args": ["string"]} +{"name": "rtrim", "args": ["string", "string"]} +{"name": "lpad", "args": ["string", "int64"]} +{"name": "lpad", "args": ["string", "int64", "string"]} +{"name": "lpad", "args": ["bytes", "int64", "bytes"]} +{"name": "rpad", "args": ["string", "int64"]} +{"name": "rpad", "args": ["string", "int64", "string"]} +{"name": "rpad", "args": ["bytes", "int64", "bytes"]} +{"name": "substr", "args": ["string", "int64"]} +{"name": "substr", "args": ["string", "int64", "int64"]} +{"name": "substr", "args": ["bytes", "int64"]} +{"name": "substr", "args": ["bytes", "int64", "int64"]} +{"name": "substring", "args": ["string", "int64"]} +{"name": "substring", "args": ["string", "int64", "int64"]} +{"name": "strpos", "args": ["string", "string"]} +{"name": "strpos", "args": ["bytes", "bytes"]} +{"name": "starts_with", "args": ["string", "string"]} +{"name": "starts_with", "args": ["bytes", "bytes"]} +{"name": "ends_with", "args": ["string", "string"]} +{"name": "ends_with", "args": ["bytes", "bytes"]} +{"name": "replace", "args": ["string", "string", "string"]} +{"name": "replace", "args": ["bytes", "bytes", "bytes"]} +{"name": "repeat", "args": ["string", "int64"]} +{"name": "repeat", "args": ["bytes", "int64"]} +{"name": "reverse", "args": ["string"]} +{"name": "reverse", "args": ["bytes"]} +{"name": "split", "args": ["string"]} +{"name": "split", "args": ["string", "string"]} +{"name": "split", "args": ["bytes", "bytes"]} +{"name": "split_substr", "args": ["string", "string", "int64"]} +{"name": "split_substr", "args": ["string", "string", "int64", "int64"]} +{"name": "regexp_contains", "args": ["string", "string='a'"]} +{"name": "regexp_contains", "args": ["bytes", "bytes=b'a'"]} +{"name": "regexp_extract", "args": ["string", "string='a'"], "nullable": true} +{"name": "regexp_extract", "args": ["bytes", "bytes=b'a'"], "nullable": true} +{"name": "regexp_extract_all", "args": ["string", "string='a'"]} +{"name": "regexp_extract_all", "args": ["bytes", "bytes=b'a'"]} +{"name": "regexp_replace", "args": ["string", "string='a'", "string"]} +{"name": "regexp_replace", "args": ["bytes", "bytes=b'a'", "bytes"]} +{"name": "format", "args": ["string"]} +{"name": "to_base64", "args": ["bytes"]} +{"name": "from_base64", "args": ["string"]} +{"name": "to_base32", "args": ["bytes"]} +{"name": "from_base32", "args": ["string"]} +{"name": "to_hex", "args": ["bytes"]} +{"name": "from_hex", "args": ["string"]} +{"name": "to_code_points", "args": ["string"]} +{"name": "to_code_points", "args": ["bytes"]} +{"name": "code_points_to_string", "args": ["array(int64)"]} +{"name": "code_points_to_bytes", "args": ["array(int64)"]} +{"name": "safe_convert_bytes_to_string", "args": ["bytes"]} +{"name": "soundex", "args": ["string"]} +{"name": "normalize", "args": ["string"]} +{"name": "normalize_and_casefold", "args": ["string"]} +{"name": "ltrim", "args": ["bytes", "bytes"]} +{"name": "rtrim", "args": ["bytes", "bytes"]} +# Dates and timestamps; date parts are passed as their keyword. +{"name": "current_date"} +{"name": "current_timestamp"} +{"name": "date", "args": ["int64", "int64", "int64"]} +{"name": "date", "args": ["timestamp"]} +{"name": "date", "args": ["timestamp", "string"]} +{"name": "date_add", "args": ["date", "interval"]} +{"name": "date_sub", "args": ["date", "interval"]} +{"name": "date_diff", "args": ["date", "date", "string=DAY"]} +{"name": "date_trunc", "args": ["date", "string=MONTH"]} +{"name": "date_from_unix_date", "args": ["int64"]} +{"name": "unix_date", "args": ["date"]} +{"name": "format_date", "args": ["string", "date"]} +{"name": "parse_date", "args": ["string", "string"]} +{"name": "timestamp", "args": ["string"]} +{"name": "timestamp", "args": ["date"]} +{"name": "timestamp", "args": ["string", "string"]} +{"name": "timestamp", "args": ["date", "string"]} +{"name": "timestamp_add", "args": ["timestamp", "interval"]} +{"name": "timestamp_sub", "args": ["timestamp", "interval"]} +{"name": "timestamp_diff", "args": ["timestamp", "timestamp", "string=SECOND"]} +{"name": "timestamp_trunc", "args": ["timestamp", "string=DAY"]} +{"name": "timestamp_trunc", "args": ["timestamp", "string=DAY", "string"]} +{"name": "timestamp_seconds", "args": ["int64"]} +{"name": "timestamp_millis", "args": ["int64"]} +{"name": "timestamp_micros", "args": ["int64"]} +{"name": "unix_seconds", "args": ["timestamp"]} +{"name": "unix_millis", "args": ["timestamp"]} +{"name": "unix_micros", "args": ["timestamp"]} +{"name": "format_timestamp", "args": ["string", "timestamp"]} +{"name": "format_timestamp", "args": ["string", "timestamp", "string"]} +{"name": "parse_timestamp", "args": ["string", "string"]} +{"name": "parse_timestamp", "args": ["string", "string", "string"]} +{"name": "string", "args": ["timestamp"]} +{"name": "string", "args": ["timestamp", "string"]} +{"name": "string", "args": ["json"]} +{"name": "make_interval", "args": ["int64", "int64", "int64"]} +{"name": "justify_days", "args": ["interval"]} +{"name": "justify_hours", "args": ["interval"]} +{"name": "justify_interval", "args": ["interval"]} +# JSON. +{"name": "json_value", "args": ["json", "string='$.a'"], "nullable": true} +{"name": "json_value", "args": ["string", "string='$.a'"], "nullable": true} +{"name": "json_query", "args": ["json", "string='$.a'"], "nullable": true} +{"name": "json_query", "args": ["string", "string='$.a'"], "nullable": true} +{"name": "json_query_array", "args": ["json", "string='$.a'"], "nullable": true} +{"name": "json_query_array", "args": ["json"], "nullable": true} +{"name": "json_value_array", "args": ["json", "string='$.a'"], "nullable": true} +{"name": "json_value_array", "args": ["json"], "nullable": true} +{"name": "json_type", "args": ["json"]} +{"name": "json_keys", "args": ["json"]} +{"name": "json_strip_nulls", "args": ["json"]} +{"name": "parse_json", "args": ["string"]} +{"name": "to_json", "args": ["T"], "returns": "json", "never_null": true} +{"name": "to_json_string", "args": ["json"], "never_null": true} +{"name": "safe_to_json", "args": ["T"], "returns": "json", "never_null": true} +{"name": "bool", "args": ["json"]} +{"name": "int64", "args": ["json"]} +{"name": "float64", "args": ["json"]} +{"name": "float32", "args": ["json"]} +{"name": "lax_bool", "args": ["json"], "nullable": true} +{"name": "lax_int64", "args": ["json"], "nullable": true} +{"name": "lax_float64", "args": ["json"], "nullable": true} +{"name": "lax_string", "args": ["json"], "nullable": true} +{"name": "json_array", "args": ["T"], "returns": "json"} +{"name": "json_object", "args": ["string", "T"], "returns": "json"} +{"name": "json_set", "args": ["json", "string='$.a'", "T"], "returns": "json"} +{"name": "json_remove", "args": ["json", "string='$.a'"]} +{"name": "json_array_append", "args": ["json", "string='$'", "T"], "returns": "json"} +{"name": "json_array_insert", "args": ["json", "string='$[0]'", "T"], "returns": "json"} +{"name": "json_contains", "args": ["json", "json"]} +# Arrays. +{"name": "array_length", "args": ["AT"], "returns": "int64"} +{"name": "array_to_string", "args": ["array(string)", "string"]} +{"name": "array_to_string", "args": ["array(string)", "string", "string"]} +{"name": "array_to_string", "args": ["array(bytes)", "bytes"]} +{"name": "array_reverse", "args": ["AT"], "returns": "$1"} +{"name": "array_concat", "args": ["AT", "AT"], "returns": "$1"} +{"name": "array_first", "args": ["AT"], "returns": "anyelement", "nullable": true} +{"name": "array_last", "args": ["AT"], "returns": "anyelement", "nullable": true} +{"name": "array_min", "args": ["AT"], "returns": "anyelement", "nullable": true} +{"name": "array_max", "args": ["AT"], "returns": "anyelement", "nullable": true} +{"name": "array_slice", "args": ["AT", "int64", "int64"], "returns": "$1"} +{"name": "array_includes", "args": ["AT", "T"], "returns": "bool"} +{"name": "array_includes_any", "args": ["AT", "AT"], "returns": "bool"} +{"name": "array_includes_all", "args": ["AT", "AT"], "returns": "bool"} +{"name": "array_is_distinct", "args": ["AT"], "returns": "bool"} +{"name": "generate_array", "args": ["int64", "int64"]} +{"name": "generate_array", "args": ["int64", "int64", "int64"]} +{"name": "generate_array", "args": ["float64", "float64", "float64"]} +{"name": "generate_date_array", "args": ["date", "date"]} +{"name": "generate_date_array", "args": ["date", "date", "interval"]} +# Hashing, ids and sequences. +{"name": "farm_fingerprint", "args": ["string"]} +{"name": "farm_fingerprint", "args": ["bytes"]} +{"name": "md5", "args": ["string"]} +{"name": "md5", "args": ["bytes"]} +{"name": "sha1", "args": ["string"]} +{"name": "sha1", "args": ["bytes"]} +{"name": "sha256", "args": ["string"]} +{"name": "sha256", "args": ["bytes"]} +{"name": "sha512", "args": ["string"]} +{"name": "sha512", "args": ["bytes"]} +{"name": "generate_uuid", "never_null": true} +{"name": "new_uuid", "never_null": true} +# Search and vectors. +{"name": "search", "args": ["tokenlist", "string='a'"]} +{"name": "search_substring", "args": ["tokenlist", "string='a'"]} +{"name": "search_ngrams", "args": ["tokenlist", "string='a'"]} +{"name": "score", "args": ["tokenlist", "string='a'"]} +{"name": "score_ngrams", "args": ["tokenlist", "string='a'"]} +{"name": "snippet", "args": ["string", "string='a'"]} +{"name": "cosine_distance", "args": ["array(float64)", "array(float64)"]} +{"name": "cosine_distance", "args": ["array(float32)", "array(float32)"]} +{"name": "euclidean_distance", "args": ["array(float64)", "array(float64)"]} +{"name": "euclidean_distance", "args": ["array(float32)", "array(float32)"]} +{"name": "dot_product", "args": ["array(float64)", "array(float64)"]} +{"name": "dot_product", "args": ["array(float32)", "array(float32)"]} +{"name": "dot_product", "args": ["array(int64)", "array(int64)"]} +# Calls a select list cannot spell as name(args): the expression or the +# whole statement to compile instead, with $1, $2... for the arguments. A +# statement whose result is not the call names what the call returns. +{"name": "extract", "args": ["timestamp"], "expr": "EXTRACT(YEAR FROM $1)"} +{"name": "extract", "args": ["timestamp", "string"], "expr": "EXTRACT(YEAR FROM $1 AT TIME ZONE $2)"} +{"name": "extract", "args": ["date"], "expr": "EXTRACT(YEAR FROM $1)"} +{"name": "get_next_sequence_value", "args": ["anyelement"], "expr": "GET_NEXT_SEQUENCE_VALUE(SEQUENCE probe_sequence)", "never_null": true} +{"name": "pending_commit_timestamp", "query": "INSERT INTO probe_commits (id, committed_at) VALUES (1, PENDING_COMMIT_TIMESTAMP())", "returns": "timestamp", "never_null": true} +{"name": "search", "args": ["tokenlist", "string='a'"], "query": "SELECT id FROM probe WHERE SEARCH($1, $2)", "returns": "bool"} +{"name": "search_substring", "args": ["tokenlist", "string='a'"], "query": "SELECT id FROM probe WHERE SEARCH_SUBSTRING(tl_substring, $2)", "returns": "bool"} +{"name": "search_ngrams", "args": ["tokenlist", "string='a'"], "query": "SELECT id FROM probe WHERE SEARCH_NGRAMS(tl_ngrams, $2)", "returns": "bool"} +{"name": "score", "args": ["tokenlist", "string='a'"], "query": "SELECT SCORE($1, $2) FROM probe WHERE SEARCH($1, $2)"} +{"name": "score_ngrams", "args": ["tokenlist", "string='a'"], "query": "SELECT SCORE_NGRAMS(tl_ngrams, $2) FROM probe WHERE SEARCH_NGRAMS(tl_ngrams, $2)"} +# unnest stands for UNNEST(array) in FROM and IN, which sqlc reads as a +# call; its element can be NULL. +{"name": "unnest", "args": ["AT"], "returns": "anyelement", "expr": "(SELECT e FROM UNNEST($1) AS e LIMIT 1)", "nullable": true} diff --git a/internal/goldeneye/spanner/spanner.go b/internal/goldeneye/spanner/spanner.go index 90d82a13fe..61a8858fd0 100644 --- a/internal/goldeneye/spanner/spanner.go +++ b/internal/goldeneye/spanner/spanner.go @@ -6,11 +6,13 @@ // googlesql directory, since sqlc's engine is named after the language // Spanner speaks rather than the database. // -// Spanner keeps no catalog of its types, functions or operators, so -// types.jsonl, functions.jsonl and operators.jsonl are written by hand and -// are not this package's business. What it does describe is its -// information schema: relations.jsonl is every view of INFORMATION_SCHEMA -// and SPANNER_SYS, read from INFORMATION_SCHEMA itself. +// relations.jsonl is every view of INFORMATION_SCHEMA and SPANNER_SYS, +// read from INFORMATION_SCHEMA itself. Spanner keeps no catalog of its +// types, functions or operators, so types.jsonl, functions.jsonl and +// operators.jsonl are read by asking: library.jsonl lists the function +// signatures to try, and each is compiled in PLAN mode against a probe +// database, the ones Spanner accepts kept with the type it reports; the +// operators and types are tried the same way (see library.go). // // The package also verifies the GoogleSQL analyze cases against the same // server: each case's schema becomes a database of its own, its fixture is @@ -193,7 +195,8 @@ func (s *server) query(ctx context.Context, session, sql string) ([][]*structpb. // Generate reads the dialect from the server: relations.jsonl, the views of // INFORMATION_SCHEMA and SPANNER_SYS, read from a database of their own, -// since every database has the same ones. +// since every database has the same ones, and the types, functions and +// operators, read from a probe database. func Generate(ctx context.Context, endpoint string) (dialect.Files, error) { s, err := open(ctx, endpoint) if err != nil { @@ -220,5 +223,23 @@ func Generate(ctx context.Context, endpoint string) (dialect.Files, error) { if err != nil { return nil, err } - return dialect.Files{dialect.RelationsFile: blob}, nil + + lib := Instance + "/databases/" + libraryDatabase + if err := s.dropDatabase(ctx, lib); err != nil { + return nil, err + } + if _, err := s.createDatabase(ctx, libraryDatabase, librarySchema); err != nil { + return nil, err + } + defer s.dropDatabase(context.WithoutCancel(ctx), lib) + libSession, err := s.session(ctx, lib) + if err != nil { + return nil, err + } + files, err := readLibrary(ctx, s, libSession) + if err != nil { + return nil, err + } + files[dialect.RelationsFile] = blob + return files, nil }