diff --git a/docs/reference/environment-variables.md b/docs/reference/environment-variables.md index 291c578c02..dde6b40812 100644 --- a/docs/reference/environment-variables.md +++ b/docs/reference/environment-variables.md @@ -14,8 +14,17 @@ SQLCEXPERIMENT=nofoo # explicitly disable foo experiment SQLCEXPERIMENT=foo,nobar # enable foo, disable bar ``` -Currently, no experiments are defined. Experiments will be documented here as -they are introduced. +The following experiments are defined: + +### coreanalyzer + +Routes `sqlc generate` through the core catalog and analyzer instead of each +engine's own analysis path. This is the same analysis used by `sqlc analyze`, +and the only analysis path for the ClickHouse and GoogleSQL engines. + +``` +SQLCEXPERIMENT=coreanalyzer +``` ## SQLCCACHE diff --git a/internal/cmd/generate.go b/internal/cmd/generate.go index 6d2a0297b6..fbdb084409 100644 --- a/internal/cmd/generate.go +++ b/internal/cmd/generate.go @@ -254,7 +254,11 @@ func (g *generator) ProcessResult(ctx context.Context, combo config.CombinedSett func parse(ctx context.Context, name, dir string, sql config.SQL, combo config.CombinedSettings, parserOpts opts.Parser, stderr io.Writer) (*compiler.Result, bool) { defer trace.StartRegion(ctx, "parse").End() - c, err := compiler.NewCompiler(sql, combo, parserOpts) + var copts []compiler.Option + if parserOpts.Experiment.CoreAnalyzer { + copts = append(copts, compiler.WithCoreAnalysis()) + } + c, err := compiler.NewCompiler(sql, combo, parserOpts, copts...) defer func() { if c != nil { c.Close(ctx) diff --git a/internal/cmd/process.go b/internal/cmd/process.go index e90450cf5f..e54eb43b39 100644 --- a/internal/cmd/process.go +++ b/internal/cmd/process.go @@ -86,7 +86,9 @@ func processQuerySets(ctx context.Context, rp ResultProcessor, conf *config.Conf sql.Queries = joined var name, lang string - parseOpts := opts.Parser{} + parseOpts := opts.Parser{ + Experiment: o.Env.Experiment, + } switch { case sql.Gen.Go != nil: diff --git a/internal/compiler/catalog_core.go b/internal/compiler/catalog_core.go new file mode 100644 index 0000000000..ce89416926 --- /dev/null +++ b/internal/compiler/catalog_core.go @@ -0,0 +1,56 @@ +package compiler + +import ( + "strings" + + "github.com/sqlc-dev/sqlc/internal/core" + "github.com/sqlc-dev/sqlc/internal/sql/ast" + "github.com/sqlc-dev/sqlc/internal/sql/catalog" +) + +// coreResultCatalog dumps the core catalog into the legacy catalog shape a +// Result carries, so codegen sees the same table models either way a query +// set was analyzed. Only relations make the trip: codegen reads tables and +// their columns to build models, and none of the types, functions or +// operators the core catalog also holds. +func coreResultCatalog(c *core.Catalog) (*catalog.Catalog, error) { + cat := catalog.New("public") + namespaces, err := c.Namespaces() + if err != nil { + return nil, err + } + for _, ns := range namespaces { + schema := &catalog.Schema{Name: ns.Name} + tables, err := c.TablesInNamespace(ns.OID) + if err != nil { + return nil, err + } + for _, table := range tables { + cols, err := c.ClassCodegenColumns(table.OID) + if err != nil { + return nil, err + } + t := &catalog.Table{Rel: &ast.TableName{Schema: ns.Name, Name: table.Name}} + for _, col := range cols { + // The catalog names an array type after its element with the + // suffix appended, which is codegen's data type and array + // flag in one string. The core catalog holds one dimension, + // and codegen renders a "[]" per dimension. + dataType, isArray := strings.CutSuffix(col.TypeName, core.ArraySuffix) + column := &catalog.Column{ + Name: col.Name, + Type: ast.TypeName{Name: dataType}, + IsNotNull: col.NotNull, + IsArray: isArray, + } + if isArray { + column.ArrayDims = 1 + } + t.Columns = append(t.Columns, column) + } + schema.Tables = append(schema.Tables, t) + } + cat.Schemas = append(cat.Schemas, schema) + } + return cat, nil +} diff --git a/internal/compiler/compile.go b/internal/compiler/compile.go index 83851d7f3c..b6f703d1cb 100644 --- a/internal/compiler/compile.go +++ b/internal/compiler/compile.go @@ -142,6 +142,13 @@ func (c *Compiler) parseCatalogCore(files []schemaFile, merr *multierr.Error) er } // Whatever apply reported is already in merr, which the caller returns. c.coreCatalog = cat + + // Codegen consumes the catalog through the Result, in the legacy shape. + legacy, err := coreResultCatalog(cat) + if err != nil { + return fmt.Errorf("%s: dump catalog: %w", c.conf.Engine, err) + } + c.catalog = legacy return nil } diff --git a/internal/compiler/parse_core.go b/internal/compiler/parse_core.go index 5dae66f48f..920d3140fa 100644 --- a/internal/compiler/parse_core.go +++ b/internal/compiler/parse_core.go @@ -94,6 +94,11 @@ func coreColumn(c core.Column) *Column { NotNull: c.NotNull, IsArray: c.IsArray, } + // The core reports arrays without dimensions, and codegen renders one + // "[]" per dimension. + if c.IsArray { + col.ArrayDims = 1 + } if c.Source != nil && c.Source.Table != "" { col.Table = &ast.TableName{Schema: c.Source.Schema, Name: c.Source.Table} col.TableAlias = c.Source.TableAlias @@ -113,6 +118,9 @@ func coreParamColumn(p core.Parameter, params *named.ParamSet) *Column { NotNull: p.NotNull, IsArray: p.IsArray, } + if p.IsArray { + col.ArrayDims = 1 + } if p.Source != nil && p.Source.Table != "" { col.Table = &ast.TableName{Schema: p.Source.Schema, Name: p.Source.Table} col.OriginalName = p.Source.Column diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/mysql/exec.json b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/exec.json new file mode 100644 index 0000000000..8da46d61b5 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/exec.json @@ -0,0 +1,6 @@ +{ + "command": "generate", + "env": { + "SQLCEXPERIMENT": "coreanalyzer" + } +} diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/mysql/go/db.go b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/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, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/mysql/go/models.go b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/go/models.go new file mode 100644 index 0000000000..2208139112 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/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 Author struct { + ID int64 + Name string + Bio sql.NullString +} diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/mysql/go/query.sql.go b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/go/query.sql.go new file mode 100644 index 0000000000..ea37ba27da --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/go/query.sql.go @@ -0,0 +1,81 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const createAuthor = `-- name: CreateAuthor :execresult +INSERT INTO authors (id, name, bio) +VALUES (?, ?, ?) +` + +type CreateAuthorParams struct { + ID int64 + Name string + Bio sql.NullString +} + +func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (sql.Result, error) { + return q.db.ExecContext(ctx, createAuthor, arg.ID, arg.Name, arg.Bio) +} + +const deleteAuthor = `-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE id = ? +` + +func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, deleteAuthor, id) + return err +} + +const getAuthor = `-- name: GetAuthor :one +SELECT * FROM authors +WHERE id = ? +` + +func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) { + row := q.db.QueryRowContext(ctx, getAuthor, id) + var i Author + err := row.Scan(&i.ID, &i.Name, &i.Bio) + return i, err +} + +const listAuthors = `-- name: ListAuthors :many +SELECT id, name FROM authors +ORDER BY name +` + +type ListAuthorsRow struct { + ID int64 + Name string +} + +func (q *Queries) ListAuthors(ctx context.Context) ([]ListAuthorsRow, error) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListAuthorsRow + for rows.Next() { + var i ListAuthorsRow + 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/experiment_coreanalyzer/mysql/query.sql b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/query.sql new file mode 100644 index 0000000000..c76ee0ca8c --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/query.sql @@ -0,0 +1,15 @@ +-- name: GetAuthor :one +SELECT * FROM authors +WHERE id = ?; + +-- name: ListAuthors :many +SELECT id, name FROM authors +ORDER BY name; + +-- name: CreateAuthor :execresult +INSERT INTO authors (id, name, bio) +VALUES (?, ?, ?); + +-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE id = ?; diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/mysql/schema.sql b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/schema.sql new file mode 100644 index 0000000000..bd03609de4 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE authors ( + id bigint PRIMARY KEY, + name varchar(255) NOT NULL, + bio text +); diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/mysql/sqlc.json b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/sqlc.json new file mode 100644 index 0000000000..e41c39e8b3 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/mysql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "mysql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/exec.json b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/exec.json new file mode 100644 index 0000000000..8da46d61b5 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/exec.json @@ -0,0 +1,6 @@ +{ + "command": "generate", + "env": { + "SQLCEXPERIMENT": "coreanalyzer" + } +} diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/go/db.go b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/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, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/go/models.go b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/go/models.go new file mode 100644 index 0000000000..49c67eb514 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/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 Author struct { + ID int64 + Name string + Bio sql.NullString + Tags []string +} diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/go/query.sql.go b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/go/query.sql.go new file mode 100644 index 0000000000..706ce0e529 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/go/query.sql.go @@ -0,0 +1,97 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" + + "github.com/lib/pq" +) + +const createAuthor = `-- name: CreateAuthor :one +INSERT INTO authors (id, name, bio) +VALUES ($1, $2, $3) +RETURNING * +` + +type CreateAuthorParams struct { + ID int64 + Name string + Bio sql.NullString +} + +func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (Author, error) { + row := q.db.QueryRowContext(ctx, createAuthor, arg.ID, arg.Name, arg.Bio) + var i Author + err := row.Scan( + &i.ID, + &i.Name, + &i.Bio, + pq.Array(&i.Tags), + ) + return i, err +} + +const deleteAuthor = `-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE id = $1 +` + +func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, deleteAuthor, id) + return err +} + +const getAuthor = `-- name: GetAuthor :one +SELECT * FROM authors +WHERE id = $1 +` + +func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) { + row := q.db.QueryRowContext(ctx, getAuthor, id) + var i Author + err := row.Scan( + &i.ID, + &i.Name, + &i.Bio, + pq.Array(&i.Tags), + ) + return i, err +} + +const listAuthors = `-- name: ListAuthors :many +SELECT id, name FROM authors +ORDER BY name +` + +type ListAuthorsRow struct { + ID int64 + Name string +} + +func (q *Queries) ListAuthors(ctx context.Context) ([]ListAuthorsRow, error) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListAuthorsRow + for rows.Next() { + var i ListAuthorsRow + 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/experiment_coreanalyzer/postgresql/stdlib/query.sql b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/query.sql new file mode 100644 index 0000000000..48bc0dd304 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/query.sql @@ -0,0 +1,16 @@ +-- name: GetAuthor :one +SELECT * FROM authors +WHERE id = $1; + +-- name: ListAuthors :many +SELECT id, name FROM authors +ORDER BY name; + +-- name: CreateAuthor :one +INSERT INTO authors (id, name, bio) +VALUES ($1, $2, $3) +RETURNING *; + +-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE id = $1; diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/schema.sql b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/schema.sql new file mode 100644 index 0000000000..a12bc54b3e --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE authors ( + id bigint PRIMARY KEY, + name text NOT NULL, + bio text, + tags text[] +); diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/sqlc.json b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/sqlc.json new file mode 100644 index 0000000000..f717ca2e66 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/postgresql/stdlib/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "postgresql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/exec.json b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/exec.json new file mode 100644 index 0000000000..8da46d61b5 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/exec.json @@ -0,0 +1,6 @@ +{ + "command": "generate", + "env": { + "SQLCEXPERIMENT": "coreanalyzer" + } +} diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/go/db.go b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/go/db.go new file mode 100644 index 0000000000..80dd6ab1f6 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/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, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx *sql.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/go/models.go b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/go/models.go new file mode 100644 index 0000000000..2208139112 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/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 Author struct { + ID int64 + Name string + Bio sql.NullString +} diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/go/query.sql.go b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/go/query.sql.go new file mode 100644 index 0000000000..db27b7ced6 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/go/query.sql.go @@ -0,0 +1,85 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const createAuthor = `-- name: CreateAuthor :one +INSERT INTO authors (id, name, bio) +VALUES (?, ?, ?) +RETURNING * +` + +type CreateAuthorParams struct { + ID int64 + Name string + Bio sql.NullString +} + +func (q *Queries) CreateAuthor(ctx context.Context, arg CreateAuthorParams) (Author, error) { + row := q.db.QueryRowContext(ctx, createAuthor, arg.ID, arg.Name, arg.Bio) + var i Author + err := row.Scan(&i.ID, &i.Name, &i.Bio) + return i, err +} + +const deleteAuthor = `-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE id = ? +` + +func (q *Queries) DeleteAuthor(ctx context.Context, id int64) error { + _, err := q.db.ExecContext(ctx, deleteAuthor, id) + return err +} + +const getAuthor = `-- name: GetAuthor :one +SELECT * FROM authors +WHERE id = ? +` + +func (q *Queries) GetAuthor(ctx context.Context, id int64) (Author, error) { + row := q.db.QueryRowContext(ctx, getAuthor, id) + var i Author + err := row.Scan(&i.ID, &i.Name, &i.Bio) + return i, err +} + +const listAuthors = `-- name: ListAuthors :many +SELECT id, name FROM authors +ORDER BY name +` + +type ListAuthorsRow struct { + ID int64 + Name string +} + +func (q *Queries) ListAuthors(ctx context.Context) ([]ListAuthorsRow, error) { + rows, err := q.db.QueryContext(ctx, listAuthors) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListAuthorsRow + for rows.Next() { + var i ListAuthorsRow + 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/experiment_coreanalyzer/sqlite/query.sql b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/query.sql new file mode 100644 index 0000000000..f8efb619a7 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/query.sql @@ -0,0 +1,16 @@ +-- name: GetAuthor :one +SELECT * FROM authors +WHERE id = ?; + +-- name: ListAuthors :many +SELECT id, name FROM authors +ORDER BY name; + +-- name: CreateAuthor :one +INSERT INTO authors (id, name, bio) +VALUES (?, ?, ?) +RETURNING *; + +-- name: DeleteAuthor :exec +DELETE FROM authors +WHERE id = ?; diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/schema.sql b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/schema.sql new file mode 100644 index 0000000000..11599c4cf7 --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/schema.sql @@ -0,0 +1,5 @@ +CREATE TABLE authors ( + id integer PRIMARY KEY, + name text NOT NULL, + bio text +); diff --git a/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/sqlc.json b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/sqlc.json new file mode 100644 index 0000000000..cd66df063b --- /dev/null +++ b/internal/endtoend/testdata/experiment_coreanalyzer/sqlite/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "sqlite", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/opts/experiment.go b/internal/opts/experiment.go index bc095dc4b2..8d4b1c1ea0 100644 --- a/internal/opts/experiment.go +++ b/internal/opts/experiment.go @@ -12,6 +12,10 @@ import ( // This is modeled after Go's GOEXPERIMENT environment variable. For more // information, see https://pkg.go.dev/internal/goexperiment // +// Available experiments: +// +// coreanalyzer - routes generate through the core catalog and analyzer +// // Example usage: // // SQLCEXPERIMENT=foo,bar # enable foo and bar experiments @@ -21,6 +25,9 @@ import ( // Experiment holds the state of all experimental features. // Add new experiments as boolean fields to this struct. type Experiment struct { + // CoreAnalyzer routes generate through the core catalog and analyzer + // instead of each engine's own analysis path. + CoreAnalyzer bool } // ExperimentFromEnv returns an Experiment initialized from the SQLCEXPERIMENT @@ -68,6 +75,8 @@ func ExperimentFromString(val string) Experiment { // known experiment. func isKnownExperiment(name string) bool { switch strings.ToLower(name) { + case "coreanalyzer": + return true default: return false } @@ -76,12 +85,17 @@ func isKnownExperiment(name string) bool { // setExperiment sets the experiment flag with the given name to the given value. func setExperiment(e *Experiment, name string, enabled bool) { switch strings.ToLower(name) { + case "coreanalyzer": + e.CoreAnalyzer = enabled } } // Enabled returns a slice of all enabled experiment names. func (e Experiment) Enabled() []string { var enabled []string + if e.CoreAnalyzer { + enabled = append(enabled, "coreanalyzer") + } return enabled } diff --git a/internal/opts/experiment_test.go b/internal/opts/experiment_test.go index 0ad6c72feb..b64204b4f7 100644 --- a/internal/opts/experiment_test.go +++ b/internal/opts/experiment_test.go @@ -43,6 +43,26 @@ func TestExperimentFromString(t *testing.T) { input: "foo,,bar", want: Experiment{}, }, + { + name: "enable coreanalyzer", + input: "coreanalyzer", + want: Experiment{CoreAnalyzer: true}, + }, + { + name: "disable coreanalyzer", + input: "nocoreanalyzer", + want: Experiment{CoreAnalyzer: false}, + }, + { + name: "enable then disable coreanalyzer", + input: "coreanalyzer,nocoreanalyzer", + want: Experiment{CoreAnalyzer: false}, + }, + { + name: "coreanalyzer case insensitive", + input: "CoreAnalyzer", + want: Experiment{CoreAnalyzer: true}, + }, } for _, tt := range tests { @@ -56,9 +76,36 @@ func TestExperimentFromString(t *testing.T) { } func TestExperimentEnabled(t *testing.T) { - exp := Experiment{} - if got := exp.Enabled(); len(got) != 0 { - t.Errorf("Experiment.Enabled() = %v, want none", got) + tests := []struct { + name string + exp Experiment + want []string + }{ + { + name: "no experiments enabled", + exp: Experiment{}, + want: nil, + }, + { + name: "coreanalyzer enabled", + exp: Experiment{CoreAnalyzer: true}, + want: []string{"coreanalyzer"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.exp.Enabled() + if len(got) != len(tt.want) { + t.Errorf("Experiment.Enabled() = %v, want %v", got, tt.want) + return + } + for i := range got { + if got[i] != tt.want[i] { + t.Errorf("Experiment.Enabled()[%d] = %q, want %q", i, got[i], tt.want[i]) + } + } + }) } } @@ -85,6 +132,16 @@ func TestIsKnownExperiment(t *testing.T) { input: "", want: false, }, + { + name: "coreanalyzer lowercase", + input: "coreanalyzer", + want: true, + }, + { + name: "coreanalyzer mixed case", + input: "CoreAnalyzer", + want: true, + }, } for _, tt := range tests {