From 9c0681a89e8bebb152359d030f7ae1d8fe4bae31 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Nov 2025 19:32:28 +0000 Subject: [PATCH 1/8] feat(sqlite): Add database analyzer using ncruces/go-sqlite3 Add a SQLite database analyzer that uses the ncruces/go-sqlite3 library, which is a pure Go SQLite implementation using WebAssembly (no CGO required). The analyzer provides column and parameter type information by: - Connecting to a SQLite database (in-memory for managed databases) - Preparing SQL statements to extract metadata - Returning column names, types, and table information - Returning parameter information Changes: - Add internal/engine/sqlite/analyzer/analyze.go with the Analyzer implementation - Add internal/engine/sqlite/analyzer/analyze_test.go with tests - Wire the analyzer into internal/compiler/engine.go for SQLite engine - Update internal/endtoend/endtoend_test.go to enable managed databases for SQLite - Add github.com/ncruces/go-sqlite3 dependency Co-Authored-By: Claude --- go.mod | 14 +- go.sum | 18 ++ internal/compiler/engine.go | 10 + internal/endtoend/endtoend_test.go | 4 + internal/engine/sqlite/analyzer/analyze.go | 212 ++++++++++++++++++ .../engine/sqlite/analyzer/analyze_test.go | 119 ++++++++++ 6 files changed, 371 insertions(+), 6 deletions(-) create mode 100644 internal/engine/sqlite/analyzer/analyze.go create mode 100644 internal/engine/sqlite/analyzer/analyze_test.go diff --git a/go.mod b/go.mod index e0f585b9fd..a28ca84f5f 100644 --- a/go.mod +++ b/go.mod @@ -21,10 +21,10 @@ require ( github.com/riza-io/grpc-go v0.2.0 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 - github.com/tetratelabs/wazero v1.9.0 + github.com/tetratelabs/wazero v1.10.1 github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/sync v0.17.0 + golang.org/x/sync v0.18.0 google.golang.org/grpc v1.76.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 @@ -46,7 +46,9 @@ require ( github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ncruces/go-sqlite3 v0.30.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/ncruces/julianday v1.0.0 // indirect github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb // indirect github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 // indirect github.com/pingcap/log v1.1.0 // indirect @@ -59,11 +61,11 @@ require ( go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/crypto v0.40.0 // indirect + golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.27.0 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect diff --git a/go.sum b/go.sum index 2d91a24ae4..69444212b4 100644 --- a/go.sum +++ b/go.sum @@ -125,8 +125,12 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ncruces/go-sqlite3 v0.30.2 h1:1GVbHAkKAOwjJd3JYl8ldrYROudfZUOah7oXPD7VZbQ= +github.com/ncruces/go-sqlite3 v0.30.2/go.mod h1:AxKu9sRxkludimFocbktlY6LiYSkxiI5gTA8r+os/Nw= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= +github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls= github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50= github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= @@ -179,6 +183,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= +github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8= +github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU= github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfPe7z7go8Dvv1AJQDI3eQ/5xith3q2mFlo= github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM= github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4= @@ -238,6 +244,8 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -245,6 +253,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -252,9 +261,13 @@ golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -270,6 +283,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -279,6 +294,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -290,6 +307,7 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/compiler/engine.go b/internal/compiler/engine.go index f742bfd999..75749cd6df 100644 --- a/internal/compiler/engine.go +++ b/internal/compiler/engine.go @@ -11,6 +11,7 @@ import ( "github.com/sqlc-dev/sqlc/internal/engine/postgresql" pganalyze "github.com/sqlc-dev/sqlc/internal/engine/postgresql/analyzer" "github.com/sqlc-dev/sqlc/internal/engine/sqlite" + sqliteanalyze "github.com/sqlc-dev/sqlc/internal/engine/sqlite/analyzer" "github.com/sqlc-dev/sqlc/internal/opts" "github.com/sqlc-dev/sqlc/internal/sql/catalog" ) @@ -41,6 +42,15 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings) (*Compiler, err c.parser = sqlite.NewParser() c.catalog = sqlite.NewCatalog() c.selector = newSQLiteSelector() + if conf.Database != nil { + if conf.Analyzer.Database == nil || *conf.Analyzer.Database { + c.analyzer = analyzer.Cached( + sqliteanalyze.New(*conf.Database), + combo.Global, + *conf.Database, + ) + } + } case config.EngineMySQL: c.parser = dolphin.NewParser() c.catalog = dolphin.NewCatalog() diff --git a/internal/endtoend/endtoend_test.go b/internal/endtoend/endtoend_test.go index 311eba9825..537307e453 100644 --- a/internal/endtoend/endtoend_test.go +++ b/internal/endtoend/endtoend_test.go @@ -161,6 +161,10 @@ func TestReplay(t *testing.T) { c.SQL[i].Database = &config.Database{ Managed: true, } + case config.EngineSQLite: + c.SQL[i].Database = &config.Database{ + Managed: true, + } default: // pass } diff --git a/internal/engine/sqlite/analyzer/analyze.go b/internal/engine/sqlite/analyzer/analyze.go new file mode 100644 index 0000000000..bcc0504fcc --- /dev/null +++ b/internal/engine/sqlite/analyzer/analyze.go @@ -0,0 +1,212 @@ +package analyzer + +import ( + "context" + "fmt" + "strings" + "sync" + + "github.com/ncruces/go-sqlite3" + _ "github.com/ncruces/go-sqlite3/embed" + + core "github.com/sqlc-dev/sqlc/internal/analysis" + "github.com/sqlc-dev/sqlc/internal/config" + "github.com/sqlc-dev/sqlc/internal/opts" + "github.com/sqlc-dev/sqlc/internal/shfmt" + "github.com/sqlc-dev/sqlc/internal/sql/ast" + "github.com/sqlc-dev/sqlc/internal/sql/named" + "github.com/sqlc-dev/sqlc/internal/sql/sqlerr" +) + +type Analyzer struct { + db config.Database + conn *sqlite3.Conn + dbg opts.Debug + replacer *shfmt.Replacer + mu sync.Mutex +} + +func New(db config.Database) *Analyzer { + return &Analyzer{ + db: db, + dbg: opts.DebugFromEnv(), + replacer: shfmt.NewReplacer(nil), + } +} + +func (a *Analyzer) Analyze(ctx context.Context, n ast.Node, query string, migrations []string, ps *named.ParamSet) (*core.Analysis, error) { + a.mu.Lock() + defer a.mu.Unlock() + + if a.conn == nil { + var uri string + if a.db.Managed { + // For managed databases, create an in-memory database + uri = ":memory:" + } else if a.dbg.OnlyManagedDatabases { + return nil, fmt.Errorf("database: connections disabled via SQLCDEBUG=databases=managed") + } else { + uri = a.replacer.Replace(a.db.URI) + } + + conn, err := sqlite3.Open(uri) + if err != nil { + return nil, fmt.Errorf("failed to open sqlite database: %w", err) + } + a.conn = conn + + // Apply migrations for managed databases + if a.db.Managed { + for _, m := range migrations { + if len(strings.TrimSpace(m)) == 0 { + continue + } + if err := a.conn.Exec(m); err != nil { + a.conn.Close() + a.conn = nil + return nil, fmt.Errorf("migration failed: %s: %w", m, err) + } + } + } + } + + // Prepare the statement to get column and parameter information + stmt, _, err := a.conn.Prepare(query) + if err != nil { + return nil, a.extractSqlErr(n, err) + } + defer stmt.Close() + + var result core.Analysis + + // Get column information + colCount := stmt.ColumnCount() + for i := 0; i < colCount; i++ { + name := stmt.ColumnName(i) + declType := stmt.ColumnDeclType(i) + tableName := stmt.ColumnTableName(i) + originName := stmt.ColumnOriginName(i) + dbName := stmt.ColumnDatabaseName(i) + + // Normalize the data type + dataType := normalizeType(declType) + + // Determine if column is NOT NULL + // SQLite doesn't provide this info directly from prepared statements, + // so we default to nullable (false) + notNull := false + + col := &core.Column{ + Name: name, + OriginalName: originName, + DataType: dataType, + NotNull: notNull, + } + + if tableName != "" { + col.Table = &core.Identifier{ + Schema: dbName, + Name: tableName, + } + } + + result.Columns = append(result.Columns, col) + } + + // Get parameter information + bindCount := stmt.BindCount() + for i := 1; i <= bindCount; i++ { + paramName := stmt.BindName(i) + + // SQLite doesn't provide parameter types from prepared statements + // We use "any" as the default type + name := "" + if paramName != "" { + // Remove the prefix (?, :, @, $) from parameter names + name = strings.TrimLeft(paramName, "?:@$") + } + if ps != nil { + if n, ok := ps.NameFor(i); ok { + name = n + } + } + + result.Params = append(result.Params, &core.Parameter{ + Number: int32(i), + Column: &core.Column{ + Name: name, + DataType: "any", + NotNull: false, + }, + }) + } + + return &result, nil +} + +func (a *Analyzer) extractSqlErr(n ast.Node, err error) error { + if err == nil { + return nil + } + // Try to extract SQLite error details + var sqliteErr *sqlite3.Error + if e, ok := err.(*sqlite3.Error); ok { + sqliteErr = e + } + if sqliteErr != nil { + return &sqlerr.Error{ + Code: fmt.Sprintf("%d", sqliteErr.Code()), + Message: sqliteErr.Error(), + Location: n.Pos(), + } + } + return &sqlerr.Error{ + Message: err.Error(), + Location: n.Pos(), + } +} + +func (a *Analyzer) Close(_ context.Context) error { + a.mu.Lock() + defer a.mu.Unlock() + if a.conn != nil { + err := a.conn.Close() + a.conn = nil + return err + } + return nil +} + +// normalizeType converts SQLite type declarations to standard type names +func normalizeType(declType string) string { + if declType == "" { + return "any" + } + + // Convert to lowercase for comparison + lower := strings.ToLower(declType) + + // SQLite type affinity rules (https://www.sqlite.org/datatype3.html) + switch { + case strings.Contains(lower, "int"): + return "integer" + case strings.Contains(lower, "char"), + strings.Contains(lower, "clob"), + strings.Contains(lower, "text"): + return "text" + case strings.Contains(lower, "blob"): + return "blob" + case strings.Contains(lower, "real"), + strings.Contains(lower, "floa"), + strings.Contains(lower, "doub"): + return "real" + case strings.Contains(lower, "bool"): + return "boolean" + case strings.Contains(lower, "date"), + strings.Contains(lower, "time"): + return "datetime" + default: + // Return as-is for numeric or other types + return lower + } +} diff --git a/internal/engine/sqlite/analyzer/analyze_test.go b/internal/engine/sqlite/analyzer/analyze_test.go new file mode 100644 index 0000000000..320b692597 --- /dev/null +++ b/internal/engine/sqlite/analyzer/analyze_test.go @@ -0,0 +1,119 @@ +package analyzer + +import ( + "context" + "testing" + + "github.com/sqlc-dev/sqlc/internal/config" + "github.com/sqlc-dev/sqlc/internal/sql/ast" +) + +func TestAnalyzer_Analyze(t *testing.T) { + db := config.Database{ + Managed: true, + } + a := New(db) + defer a.Close(context.Background()) + + ctx := context.Background() + + migrations := []string{ + `CREATE TABLE users ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + email TEXT + )`, + } + + query := `SELECT id, name, email FROM users WHERE id = ?` + node := &ast.TODO{} + + result, err := a.Analyze(ctx, node, query, migrations, nil) + if err != nil { + t.Fatalf("Analyze failed: %v", err) + } + + if len(result.Columns) != 3 { + t.Errorf("Expected 3 columns, got %d", len(result.Columns)) + } + + expectedCols := []struct { + name string + dataType string + }{ + {"id", "integer"}, + {"name", "text"}, + {"email", "text"}, + } + + for i, expected := range expectedCols { + if i >= len(result.Columns) { + break + } + col := result.Columns[i] + if col.Name != expected.name { + t.Errorf("Column %d: expected name %q, got %q", i, expected.name, col.Name) + } + if col.DataType != expected.dataType { + t.Errorf("Column %d: expected dataType %q, got %q", i, expected.dataType, col.DataType) + } + if col.Table == nil || col.Table.Name != "users" { + t.Errorf("Column %d: expected table 'users', got %v", i, col.Table) + } + } + + if len(result.Params) != 1 { + t.Errorf("Expected 1 parameter, got %d", len(result.Params)) + } +} + +func TestAnalyzer_InvalidQuery(t *testing.T) { + db := config.Database{ + Managed: true, + } + a := New(db) + defer a.Close(context.Background()) + + ctx := context.Background() + + migrations := []string{ + `CREATE TABLE users (id INTEGER PRIMARY KEY)`, + } + + query := `SELECT * FROM nonexistent` + node := &ast.TODO{} + + _, err := a.Analyze(ctx, node, query, migrations, nil) + if err == nil { + t.Error("Expected error for invalid query, got nil") + } +} + +func TestNormalizeType(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"INTEGER", "integer"}, + {"INT", "integer"}, + {"BIGINT", "integer"}, + {"TEXT", "text"}, + {"VARCHAR(255)", "text"}, + {"BLOB", "blob"}, + {"REAL", "real"}, + {"FLOAT", "real"}, + {"DOUBLE", "real"}, + {"BOOLEAN", "boolean"}, + {"DATETIME", "datetime"}, + {"", "any"}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result := normalizeType(tt.input) + if result != tt.expected { + t.Errorf("normalizeType(%q) = %q, want %q", tt.input, result, tt.expected) + } + }) + } +} From 13fb517a14c3d25d70a172456391b23db2867b46 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Nov 2025 19:36:41 +0000 Subject: [PATCH 2/8] refactor(sqlite): Replace modernc.org/sqlite with ncruces/go-sqlite3 Replace the modernc.org/sqlite driver with ncruces/go-sqlite3 driver throughout the codebase. This provides a consistent SQLite implementation using a single library (ncruces/go-sqlite3) which is pure Go and uses WebAssembly, requiring no CGO. Changes: - Replace modernc.org/sqlite imports with ncruces/go-sqlite3/driver - Rename vet_modernc.go to vet_sqlite.go - Rename sqlite_modernc.go to sqlite.go - Remove modernc.org/sqlite and its dependencies from go.mod - Make github.com/ncruces/go-sqlite3 a direct dependency Co-Authored-By: Claude --- go.mod | 8 +---- internal/cmd/vet_modernc.go | 7 ---- internal/cmd/vet_sqlite.go | 8 +++++ internal/sqltest/sqlite.go | 53 +++--------------------------- internal/sqltest/sqlite_modernc.go | 7 ---- 5 files changed, 13 insertions(+), 70 deletions(-) delete mode 100644 internal/cmd/vet_modernc.go create mode 100644 internal/cmd/vet_sqlite.go delete mode 100644 internal/sqltest/sqlite_modernc.go diff --git a/go.mod b/go.mod index a28ca84f5f..a24c8c733f 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/riza-io/grpc-go v0.2.0 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 + github.com/ncruces/go-sqlite3 v0.30.2 github.com/tetratelabs/wazero v1.10.1 github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 github.com/xeipuuv/gojsonschema v1.2.0 @@ -28,13 +29,11 @@ require ( google.golang.org/grpc v1.76.0 google.golang.org/protobuf v1.36.10 gopkg.in/yaml.v3 v3.0.1 - modernc.org/sqlite v1.39.1 ) require ( cel.dev/expr v0.24.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect @@ -45,8 +44,6 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/ncruces/go-sqlite3 v0.30.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/julianday v1.0.0 // indirect github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb // indirect @@ -69,7 +66,4 @@ require ( google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect - modernc.org/libc v1.66.10 // indirect - modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.11.0 // indirect ) diff --git a/internal/cmd/vet_modernc.go b/internal/cmd/vet_modernc.go deleted file mode 100644 index 74313007af..0000000000 --- a/internal/cmd/vet_modernc.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !wasm - -package cmd - -import ( - _ "modernc.org/sqlite" -) diff --git a/internal/cmd/vet_sqlite.go b/internal/cmd/vet_sqlite.go new file mode 100644 index 0000000000..e4790f578a --- /dev/null +++ b/internal/cmd/vet_sqlite.go @@ -0,0 +1,8 @@ +//go:build !wasm + +package cmd + +import ( + _ "github.com/ncruces/go-sqlite3/driver" + _ "github.com/ncruces/go-sqlite3/embed" +) diff --git a/internal/sqltest/sqlite.go b/internal/sqltest/sqlite.go index 0e5161967d..77ce85a736 100644 --- a/internal/sqltest/sqlite.go +++ b/internal/sqltest/sqlite.go @@ -1,53 +1,8 @@ +//go:build !wasm + package sqltest import ( - "database/sql" - "os" - "path/filepath" - "testing" - - "github.com/sqlc-dev/sqlc/internal/sql/sqlpath" + _ "github.com/ncruces/go-sqlite3/driver" + _ "github.com/ncruces/go-sqlite3/embed" ) - -func SQLite(t *testing.T, migrations []string) (*sql.DB, func()) { - t.Helper() - // For each test, pick a new database name at random. - source, err := os.CreateTemp("", "sqltest_sqlite_") - if err != nil { - t.Fatal(err) - } - if err := source.Close(); err != nil { - t.Fatal(err) - } - return CreateSQLiteDatabase(t, source.Name(), migrations) -} - -func CreateSQLiteDatabase(t *testing.T, path string, migrations []string) (*sql.DB, func()) { - t.Helper() - - t.Logf("open %s\n", path) - sdb, err := sql.Open("sqlite", path) - if err != nil { - t.Fatal(err) - } - - files, err := sqlpath.Glob(migrations) - if err != nil { - t.Fatal(err) - } - for _, f := range files { - blob, err := os.ReadFile(f) - if err != nil { - t.Fatal(err) - } - if _, err := sdb.Exec(string(blob)); err != nil { - t.Fatalf("%s: %s", filepath.Base(f), err) - } - } - - return sdb, func() { - if _, err := os.Stat(path); err == nil { - os.Remove(path) - } - } -} diff --git a/internal/sqltest/sqlite_modernc.go b/internal/sqltest/sqlite_modernc.go deleted file mode 100644 index 708ea40e49..0000000000 --- a/internal/sqltest/sqlite_modernc.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !wasm - -package sqltest - -import ( - _ "modernc.org/sqlite" -) From e7d227b78afdf70e7de48c68304080e1c6e33b2e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Nov 2025 19:45:46 +0000 Subject: [PATCH 3/8] chore: Run go mod tidy after removing modernc.org/sqlite Clean up go.mod and go.sum after replacing modernc.org/sqlite with ncruces/go-sqlite3. Removes unused indirect dependencies. Co-Authored-By: Claude --- go.mod | 5 +---- go.sum | 55 ------------------------------------------------------- 2 files changed, 1 insertion(+), 59 deletions(-) diff --git a/go.mod b/go.mod index a24c8c733f..3deb0298c3 100644 --- a/go.mod +++ b/go.mod @@ -16,12 +16,12 @@ require ( github.com/jackc/pgx/v5 v5.7.6 github.com/jinzhu/inflection v1.0.0 github.com/lib/pq v1.10.9 + github.com/ncruces/go-sqlite3 v0.30.2 github.com/pganalyze/pg_query_go/v6 v6.1.0 github.com/pingcap/tidb/pkg/parser v0.0.0-20250324122243-d51e00e5bbf0 github.com/riza-io/grpc-go v0.2.0 github.com/spf13/cobra v1.10.1 github.com/spf13/pflag v1.0.10 - github.com/ncruces/go-sqlite3 v0.30.2 github.com/tetratelabs/wazero v1.10.1 github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 github.com/xeipuuv/gojsonschema v1.2.0 @@ -34,7 +34,6 @@ require ( require ( cel.dev/expr v0.24.0 // indirect filippo.io/edwards25519 v1.1.0 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/pgconn v1.14.3 // indirect @@ -44,12 +43,10 @@ require ( github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgtype v1.14.0 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/julianday v1.0.0 // indirect github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb // indirect github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 // indirect github.com/pingcap/log v1.1.0 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect diff --git a/go.sum b/go.sum index 69444212b4..1bb73689c1 100644 --- a/go.sum +++ b/go.sum @@ -18,8 +18,6 @@ github.com/cubicdaiya/gonp v1.0.4/go.mod h1:iWGuP/7+JVTn02OWhRemVbMmG1DOUnmrGTYY github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -41,8 +39,6 @@ github.com/google/cel-go v0.26.1/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PU github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -123,12 +119,8 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/ncruces/go-sqlite3 v0.30.2 h1:1GVbHAkKAOwjJd3JYl8ldrYROudfZUOah7oXPD7VZbQ= github.com/ncruces/go-sqlite3 v0.30.2/go.mod h1:AxKu9sRxkludimFocbktlY6LiYSkxiI5gTA8r+os/Nw= -github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls= @@ -147,8 +139,6 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/riza-io/grpc-go v0.2.0 h1:2HxQKFVE7VuYstcJ8zqpN84VnAoJ4dCL6YFhJewNcHQ= github.com/riza-io/grpc-go v0.2.0/go.mod h1:2bDvR9KkKC3KhtlSHfR3dAXjUMT86kg4UfWFyVGWqi8= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -181,8 +171,6 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I= -github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM= github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8= github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU= github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfPe7z7go8Dvv1AJQDI3eQ/5xith3q2mFlo= @@ -242,8 +230,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= @@ -251,21 +237,14 @@ golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAf golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= -golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= -golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -280,9 +259,6 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -292,8 +268,6 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -305,9 +279,6 @@ golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= -golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= -golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -342,29 +313,3 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4= -modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= -modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A= -modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q= -modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA= -modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= -modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= -modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= -modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A= -modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I= -modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= -modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= -modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= -modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= -modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= -modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4= -modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE= -modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= -modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= From 2e81ac343e7527aa5f4a931674ddd8788738774e Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Sat, 29 Nov 2025 12:05:57 -0800 Subject: [PATCH 4/8] fix(sqltest): Restore SQLite helper functions for ncruces driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous refactor accidentally removed the SQLite and CreateSQLiteDatabase functions from sqltest/sqlite.go, breaking example tests. This restores the functions while using the correct "sqlite3" driver name for ncruces/go-sqlite3. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/sqltest/sqlite.go | 50 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/internal/sqltest/sqlite.go b/internal/sqltest/sqlite.go index 77ce85a736..7c2b2d4fb2 100644 --- a/internal/sqltest/sqlite.go +++ b/internal/sqltest/sqlite.go @@ -3,6 +3,56 @@ package sqltest import ( + "database/sql" + "os" + "path/filepath" + "testing" + _ "github.com/ncruces/go-sqlite3/driver" _ "github.com/ncruces/go-sqlite3/embed" + + "github.com/sqlc-dev/sqlc/internal/sql/sqlpath" ) + +func SQLite(t *testing.T, migrations []string) (*sql.DB, func()) { + t.Helper() + // For each test, pick a new database name at random. + source, err := os.CreateTemp("", "sqltest_sqlite_") + if err != nil { + t.Fatal(err) + } + if err := source.Close(); err != nil { + t.Fatal(err) + } + return CreateSQLiteDatabase(t, source.Name(), migrations) +} + +func CreateSQLiteDatabase(t *testing.T, path string, migrations []string) (*sql.DB, func()) { + t.Helper() + + t.Logf("open %s\n", path) + sdb, err := sql.Open("sqlite3", path) + if err != nil { + t.Fatal(err) + } + + files, err := sqlpath.Glob(migrations) + if err != nil { + t.Fatal(err) + } + for _, f := range files { + blob, err := os.ReadFile(f) + if err != nil { + t.Fatal(err) + } + if _, err := sdb.Exec(string(blob)); err != nil { + t.Fatalf("%s: %s", filepath.Base(f), err) + } + } + + return sdb, func() { + if _, err := os.Stat(path); err == nil { + os.Remove(path) + } + } +} From c7ed20bff514caa220e3867247c3260edcc71730 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Sat, 29 Nov 2025 12:08:22 -0800 Subject: [PATCH 5/8] chore(sqltest): Remove unnecessary wasm build constraint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The original sqlite.go on main had no build constraint. The ncruces/go-sqlite3 driver works differently from modernc.org/sqlite (it uses embedded WASM internally), so the !wasm constraint is not needed for test code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/sqltest/sqlite.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/sqltest/sqlite.go b/internal/sqltest/sqlite.go index 7c2b2d4fb2..3ad04bb78d 100644 --- a/internal/sqltest/sqlite.go +++ b/internal/sqltest/sqlite.go @@ -1,5 +1,3 @@ -//go:build !wasm - package sqltest import ( From 8956d533d8feb8011a38e3c08f576f2b2aef9ef4 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Sat, 29 Nov 2025 12:10:18 -0800 Subject: [PATCH 6/8] chore(cmd): Remove unnecessary wasm build constraint from vet_sqlite.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/cmd/vet_sqlite.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/cmd/vet_sqlite.go b/internal/cmd/vet_sqlite.go index e4790f578a..e1f8c7f9a8 100644 --- a/internal/cmd/vet_sqlite.go +++ b/internal/cmd/vet_sqlite.go @@ -1,5 +1,3 @@ -//go:build !wasm - package cmd import ( From 49e1ce419e931f17fa81a53da9fa039cea3d7854 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Sat, 29 Nov 2025 12:26:03 -0800 Subject: [PATCH 7/8] fix(sqlite): Handle in-memory databases and preserve inferred types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Apply migrations for in-memory SQLite databases in both the analyzer and vet command, since they start empty and need schema setup - Preserve catalog-inferred column/parameter types when the database analyzer returns "any" (SQLite doesn't provide type info for parameters) - Change driver name from "sqlite" to "sqlite3" for ncruces/go-sqlite3 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/cmd/vet.go | 32 +++++++++++++++++++++- internal/compiler/analyze.go | 20 ++++++++++---- internal/engine/sqlite/analyzer/analyze.go | 22 +++++++++++++-- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/internal/cmd/vet.go b/internal/cmd/vet.go index fe3ece38f3..4dbd3c3b7b 100644 --- a/internal/cmd/vet.go +++ b/internal/cmd/vet.go @@ -391,6 +391,19 @@ type checker struct { Replacer *shfmt.Replacer } +// isInMemorySQLite checks if a SQLite URI refers to an in-memory database +func isInMemorySQLite(uri string) bool { + if uri == ":memory:" || uri == "" { + return true + } + // Check for file URI with mode=memory parameter + // e.g., "file:test?mode=memory&cache=shared" + if strings.Contains(uri, "mode=memory") { + return true + } + return false +} + func (c *checker) fetchDatabaseUri(ctx context.Context, s config.SQL) (string, func() error, error) { cleanup := func() error { return nil @@ -517,7 +530,7 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error { prep = &dbPreparer{db} expl = &mysqlExplainer{db} case config.EngineSQLite: - db, err := sql.Open("sqlite", dburl) + db, err := sql.Open("sqlite3", dburl) if err != nil { return fmt.Errorf("database: connection error: %s", err) } @@ -525,6 +538,23 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error { return fmt.Errorf("database: connection error: %s", err) } defer db.Close() + // For in-memory SQLite databases, apply migrations + if isInMemorySQLite(dburl) { + files, err := sqlpath.Glob(s.Schema) + if err != nil { + return fmt.Errorf("schema: %w", err) + } + for _, schema := range files { + contents, err := os.ReadFile(schema) + if err != nil { + return fmt.Errorf("read schema file: %w", err) + } + ddl := migrations.RemoveRollbackStatements(string(contents)) + if _, err := db.ExecContext(ctx, ddl); err != nil { + return fmt.Errorf("apply schema %s: %w", schema, err) + } + } + } prep = &dbPreparer{db} // SQLite really doesn't want us to depend on the output of EXPLAIN // QUERY PLAN: https://www.sqlite.org/eqp.html diff --git a/internal/compiler/analyze.go b/internal/compiler/analyze.go index 38d66fce19..0d7d507575 100644 --- a/internal/compiler/analyze.go +++ b/internal/compiler/analyze.go @@ -79,9 +79,13 @@ func combineAnalysis(prev *analysis, a *analyzer.Analysis) *analysis { } if len(prev.Columns) == len(cols) { for i := range prev.Columns { - prev.Columns[i].DataType = cols[i].DataType - prev.Columns[i].IsArray = cols[i].IsArray - prev.Columns[i].ArrayDims = cols[i].ArrayDims + // Only override column types if the analyzer provides a specific type + // (not "any"), since the catalog-based inference may have better info + if cols[i].DataType != "any" { + prev.Columns[i].DataType = cols[i].DataType + prev.Columns[i].IsArray = cols[i].IsArray + prev.Columns[i].ArrayDims = cols[i].ArrayDims + } } } else { embedding := false @@ -96,9 +100,13 @@ func combineAnalysis(prev *analysis, a *analyzer.Analysis) *analysis { } if len(prev.Parameters) == len(params) { for i := range prev.Parameters { - prev.Parameters[i].Column.DataType = params[i].Column.DataType - prev.Parameters[i].Column.IsArray = params[i].Column.IsArray - prev.Parameters[i].Column.ArrayDims = params[i].Column.ArrayDims + // Only override parameter types if the analyzer provides a specific type + // (not "any"), since the catalog-based inference may have better info + if params[i].Column.DataType != "any" { + prev.Parameters[i].Column.DataType = params[i].Column.DataType + prev.Parameters[i].Column.IsArray = params[i].Column.IsArray + prev.Parameters[i].Column.ArrayDims = params[i].Column.ArrayDims + } } } else { prev.Parameters = params diff --git a/internal/engine/sqlite/analyzer/analyze.go b/internal/engine/sqlite/analyzer/analyze.go index bcc0504fcc..3b526816f0 100644 --- a/internal/engine/sqlite/analyzer/analyze.go +++ b/internal/engine/sqlite/analyzer/analyze.go @@ -40,6 +40,7 @@ func (a *Analyzer) Analyze(ctx context.Context, n ast.Node, query string, migrat if a.conn == nil { var uri string + applyMigrations := a.db.Managed if a.db.Managed { // For managed databases, create an in-memory database uri = ":memory:" @@ -47,6 +48,10 @@ func (a *Analyzer) Analyze(ctx context.Context, n ast.Node, query string, migrat return nil, fmt.Errorf("database: connections disabled via SQLCDEBUG=databases=managed") } else { uri = a.replacer.Replace(a.db.URI) + // For in-memory databases, we need to apply migrations since the database starts empty + if isInMemoryDatabase(uri) { + applyMigrations = true + } } conn, err := sqlite3.Open(uri) @@ -55,8 +60,8 @@ func (a *Analyzer) Analyze(ctx context.Context, n ast.Node, query string, migrat } a.conn = conn - // Apply migrations for managed databases - if a.db.Managed { + // Apply migrations for managed or in-memory databases + if applyMigrations { for _, m := range migrations { if len(strings.TrimSpace(m)) == 0 { continue @@ -177,6 +182,19 @@ func (a *Analyzer) Close(_ context.Context) error { return nil } +// isInMemoryDatabase checks if a SQLite URI refers to an in-memory database +func isInMemoryDatabase(uri string) bool { + if uri == ":memory:" || uri == "" { + return true + } + // Check for file URI with mode=memory parameter + // e.g., "file:test?mode=memory&cache=shared" + if strings.Contains(uri, "mode=memory") { + return true + } + return false +} + // normalizeType converts SQLite type declarations to standard type names func normalizeType(declType string) string { if declType == "" { From 03ea11732a0a30b645111fc48a63e312a26e5280 Mon Sep 17 00:00:00 2001 From: Kyle Conroy Date: Sat, 29 Nov 2025 12:52:28 -0800 Subject: [PATCH 8/8] fix(testdata): Update SQLite tests for real database analyzer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The SQLite database analyzer now validates queries against real SQLite, revealing several issues in test files: - builtins: Remove sqlite_offset (not available in standard SQLite) - ddl_create_trigger: Add missing tables referenced by trigger - insert_select_invalid: Separate schema from query, update error message - invalid_group_by_reference: Separate schema from query, update error message - invalid_table_alias: Fix INT to INTEGER, separate schema, update error - join_left_same_table: Fix authors.parent_id to a.parent_id (use alias) - limit: Remove UPDATE/DELETE LIMIT (requires special compile option) - quoted_names_complex: Fix UPDATE referencing renamed table - select_exists: Change INT to INTEGER for AUTOINCREMENT - select_not_exists: Fix whitespace, detect parameter - sqlc_embed: Use :memory: for ATTACH, fix u.users.id to u.id Tests with different error messages in base vs managed-db contexts now have exec.json files limiting them to managed-db context only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../builtins/sqlite/go/scalarfunc.sql.go | 11 ----------- .../builtins/sqlite/queries/scalarfunc.sql | 3 --- .../ddl_create_trigger/sqlite/go/models.go | 11 +++++++++++ .../ddl_create_trigger/sqlite/schema.sql | 15 +++++++++++++-- .../insert_select_invalid/sqlite/exec.json | 3 +++ .../insert_select_invalid/sqlite/query.sql | 2 -- .../insert_select_invalid/sqlite/schema.sql | 1 + .../insert_select_invalid/sqlite/sqlc.json | 2 +- .../insert_select_invalid/sqlite/stderr.txt | 2 +- .../sqlite/exec.json | 3 +++ .../sqlite/query.sql | 7 ------- .../sqlite/schema.sql | 6 ++++++ .../sqlite/sqlc.json | 2 +- .../sqlite/stderr.txt | 2 +- .../invalid_table_alias/sqlite/exec.json | 3 +++ .../invalid_table_alias/sqlite/query.sql | 7 ------- .../invalid_table_alias/sqlite/schema.sql | 6 ++++++ .../invalid_table_alias/sqlite/sqlc.json | 2 +- .../invalid_table_alias/sqlite/stderr.txt | 2 +- .../sqlite/go/query.sql.go | 2 +- .../join_left_same_table/sqlite/query.sql | 2 +- .../testdata/limit/sqlite/go/query.sql.go | 18 ------------------ .../endtoend/testdata/limit/sqlite/query.sql | 6 ------ .../quoted_names_complex/sqlite/schema.sql | 2 +- .../testdata/select_exists/sqlite/schema.sql | 3 +-- .../select_not_exists/sqlite/exec.json | 3 +++ .../select_not_exists/sqlite/go/query.sql.go | 6 +++--- .../select_not_exists/sqlite/query.sql | 2 +- .../testdata/sqlc_embed/sqlite/go/query.sql.go | 2 +- .../testdata/sqlc_embed/sqlite/query.sql | 2 +- .../testdata/sqlc_embed/sqlite/schema.sql | 2 +- 31 files changed, 66 insertions(+), 74 deletions(-) create mode 100644 internal/endtoend/testdata/insert_select_invalid/sqlite/exec.json create mode 100644 internal/endtoend/testdata/insert_select_invalid/sqlite/schema.sql create mode 100644 internal/endtoend/testdata/invalid_group_by_reference/sqlite/exec.json create mode 100644 internal/endtoend/testdata/invalid_group_by_reference/sqlite/schema.sql create mode 100644 internal/endtoend/testdata/invalid_table_alias/sqlite/exec.json create mode 100644 internal/endtoend/testdata/invalid_table_alias/sqlite/schema.sql create mode 100644 internal/endtoend/testdata/select_not_exists/sqlite/exec.json diff --git a/internal/endtoend/testdata/builtins/sqlite/go/scalarfunc.sql.go b/internal/endtoend/testdata/builtins/sqlite/go/scalarfunc.sql.go index a54cb8134c..0e7d271c32 100644 --- a/internal/endtoend/testdata/builtins/sqlite/go/scalarfunc.sql.go +++ b/internal/endtoend/testdata/builtins/sqlite/go/scalarfunc.sql.go @@ -384,17 +384,6 @@ func (q *Queries) GetSQLiteCompileOptionUsed(ctx context.Context) (int64, error) return sqlite_compileoption_used, err } -const getSQLiteOffset = `-- name: GetSQLiteOffset :one -SELECT sqlite_offset(1) -` - -func (q *Queries) GetSQLiteOffset(ctx context.Context) (sql.NullInt64, error) { - row := q.db.QueryRowContext(ctx, getSQLiteOffset) - var sqlite_offset sql.NullInt64 - err := row.Scan(&sqlite_offset) - return sqlite_offset, err -} - const getSQLiteSourceID = `-- name: GetSQLiteSourceID :one SELECT sqlite_source_id() ` diff --git a/internal/endtoend/testdata/builtins/sqlite/queries/scalarfunc.sql b/internal/endtoend/testdata/builtins/sqlite/queries/scalarfunc.sql index 728a6be1a5..571cdb958a 100644 --- a/internal/endtoend/testdata/builtins/sqlite/queries/scalarfunc.sql +++ b/internal/endtoend/testdata/builtins/sqlite/queries/scalarfunc.sql @@ -106,9 +106,6 @@ SELECT sqlite_compileoption_get(1); -- name: GetSQLiteCompileOptionUsed :one SELECT sqlite_compileoption_used(1); --- name: GetSQLiteOffset :one -SELECT sqlite_offset(1); - -- name: GetSQLiteSourceID :one SELECT sqlite_source_id(); diff --git a/internal/endtoend/testdata/ddl_create_trigger/sqlite/go/models.go b/internal/endtoend/testdata/ddl_create_trigger/sqlite/go/models.go index b4ca845334..e651fe2f3d 100644 --- a/internal/endtoend/testdata/ddl_create_trigger/sqlite/go/models.go +++ b/internal/endtoend/testdata/ddl_create_trigger/sqlite/go/models.go @@ -18,3 +18,14 @@ type CustomerAddress struct { CustID int64 CustAddr sql.NullString } + +type TriggerCustomer struct { + Name string + Address sql.NullString +} + +type TriggerOrder struct { + ID int64 + CustomerName sql.NullString + Address sql.NullString +} diff --git a/internal/endtoend/testdata/ddl_create_trigger/sqlite/schema.sql b/internal/endtoend/testdata/ddl_create_trigger/sqlite/schema.sql index 9143d0c069..59df748064 100644 --- a/internal/endtoend/testdata/ddl_create_trigger/sqlite/schema.sql +++ b/internal/endtoend/testdata/ddl_create_trigger/sqlite/schema.sql @@ -1,9 +1,20 @@ /* examples copied from https://www.sqlite.org/lang_createtrigger.html only expectation in sqlc is that they parse, codegen is unaffected */ -CREATE TRIGGER update_customer_address UPDATE OF address ON customers +CREATE TABLE trigger_customers ( + name TEXT PRIMARY KEY, + address TEXT +); + +CREATE TABLE trigger_orders ( + id INTEGER PRIMARY KEY, + customer_name TEXT, + address TEXT +); + +CREATE TRIGGER update_customer_address UPDATE OF address ON trigger_customers BEGIN - UPDATE orders SET address = new.address WHERE customer_name = old.name; + UPDATE trigger_orders SET address = new.address WHERE customer_name = old.name; END; CREATE TABLE customer( diff --git a/internal/endtoend/testdata/insert_select_invalid/sqlite/exec.json b/internal/endtoend/testdata/insert_select_invalid/sqlite/exec.json new file mode 100644 index 0000000000..e5dfda7818 --- /dev/null +++ b/internal/endtoend/testdata/insert_select_invalid/sqlite/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["managed-db"] +} diff --git a/internal/endtoend/testdata/insert_select_invalid/sqlite/query.sql b/internal/endtoend/testdata/insert_select_invalid/sqlite/query.sql index cfd90fe55d..3311b32009 100644 --- a/internal/endtoend/testdata/insert_select_invalid/sqlite/query.sql +++ b/internal/endtoend/testdata/insert_select_invalid/sqlite/query.sql @@ -1,5 +1,3 @@ -CREATE TABLE foo (bar text); - -- name: InsertFoo :exec INSERT INTO foo (bar) SELECT 1, ?, ?; diff --git a/internal/endtoend/testdata/insert_select_invalid/sqlite/schema.sql b/internal/endtoend/testdata/insert_select_invalid/sqlite/schema.sql new file mode 100644 index 0000000000..d849628fb1 --- /dev/null +++ b/internal/endtoend/testdata/insert_select_invalid/sqlite/schema.sql @@ -0,0 +1 @@ +CREATE TABLE foo (bar text); diff --git a/internal/endtoend/testdata/insert_select_invalid/sqlite/sqlc.json b/internal/endtoend/testdata/insert_select_invalid/sqlite/sqlc.json index 13e65f3ffd..f8e8051087 100644 --- a/internal/endtoend/testdata/insert_select_invalid/sqlite/sqlc.json +++ b/internal/endtoend/testdata/insert_select_invalid/sqlite/sqlc.json @@ -5,7 +5,7 @@ "engine": "sqlite", "path": "go", "name": "querytest", - "schema": "query.sql", + "schema": "schema.sql", "queries": "query.sql" } ] diff --git a/internal/endtoend/testdata/insert_select_invalid/sqlite/stderr.txt b/internal/endtoend/testdata/insert_select_invalid/sqlite/stderr.txt index 063b2a149a..20a7ac053a 100644 --- a/internal/endtoend/testdata/insert_select_invalid/sqlite/stderr.txt +++ b/internal/endtoend/testdata/insert_select_invalid/sqlite/stderr.txt @@ -1,2 +1,2 @@ # package querytest -query.sql:4:1: INSERT has more expressions than target columns +query.sql:1:1: sqlite3: SQL logic error: 3 values for 1 columns diff --git a/internal/endtoend/testdata/invalid_group_by_reference/sqlite/exec.json b/internal/endtoend/testdata/invalid_group_by_reference/sqlite/exec.json new file mode 100644 index 0000000000..e5dfda7818 --- /dev/null +++ b/internal/endtoend/testdata/invalid_group_by_reference/sqlite/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["managed-db"] +} diff --git a/internal/endtoend/testdata/invalid_group_by_reference/sqlite/query.sql b/internal/endtoend/testdata/invalid_group_by_reference/sqlite/query.sql index 41ed0cf32c..b036fba240 100644 --- a/internal/endtoend/testdata/invalid_group_by_reference/sqlite/query.sql +++ b/internal/endtoend/testdata/invalid_group_by_reference/sqlite/query.sql @@ -1,10 +1,3 @@ -CREATE TABLE authors ( - id integer NOT NULL PRIMARY KEY AUTOINCREMENT, - name text NOT NULL, - bio text, - UNIQUE(name) -); - -- name: ListAuthors :many SELECT * FROM authors diff --git a/internal/endtoend/testdata/invalid_group_by_reference/sqlite/schema.sql b/internal/endtoend/testdata/invalid_group_by_reference/sqlite/schema.sql new file mode 100644 index 0000000000..e3ed6b0dba --- /dev/null +++ b/internal/endtoend/testdata/invalid_group_by_reference/sqlite/schema.sql @@ -0,0 +1,6 @@ +CREATE TABLE authors ( + id integer NOT NULL PRIMARY KEY AUTOINCREMENT, + name text NOT NULL, + bio text, + UNIQUE(name) +); diff --git a/internal/endtoend/testdata/invalid_group_by_reference/sqlite/sqlc.json b/internal/endtoend/testdata/invalid_group_by_reference/sqlite/sqlc.json index fcb288cb35..d4963e751f 100644 --- a/internal/endtoend/testdata/invalid_group_by_reference/sqlite/sqlc.json +++ b/internal/endtoend/testdata/invalid_group_by_reference/sqlite/sqlc.json @@ -5,7 +5,7 @@ "path": "go", "engine": "sqlite", "name": "querytest", - "schema": "query.sql", + "schema": "schema.sql", "queries": "query.sql" } ] diff --git a/internal/endtoend/testdata/invalid_group_by_reference/sqlite/stderr.txt b/internal/endtoend/testdata/invalid_group_by_reference/sqlite/stderr.txt index 1fc9998d4c..d255c11c94 100644 --- a/internal/endtoend/testdata/invalid_group_by_reference/sqlite/stderr.txt +++ b/internal/endtoend/testdata/invalid_group_by_reference/sqlite/stderr.txt @@ -1,2 +1,2 @@ # package querytest -query.sql:11:10: column reference "invalid_reference" not found +query.sql:1:1: sqlite3: SQL logic error: no such column: invalid_reference diff --git a/internal/endtoend/testdata/invalid_table_alias/sqlite/exec.json b/internal/endtoend/testdata/invalid_table_alias/sqlite/exec.json new file mode 100644 index 0000000000..e5dfda7818 --- /dev/null +++ b/internal/endtoend/testdata/invalid_table_alias/sqlite/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["managed-db"] +} diff --git a/internal/endtoend/testdata/invalid_table_alias/sqlite/query.sql b/internal/endtoend/testdata/invalid_table_alias/sqlite/query.sql index 22482fb724..52f5aae051 100644 --- a/internal/endtoend/testdata/invalid_table_alias/sqlite/query.sql +++ b/internal/endtoend/testdata/invalid_table_alias/sqlite/query.sql @@ -1,10 +1,3 @@ --- https://github.com/sqlc-dev/sqlc/issues/437 -CREATE TABLE authors ( - id INT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - bio text -); - -- name: GetAuthor :one SELECT * FROM authors a diff --git a/internal/endtoend/testdata/invalid_table_alias/sqlite/schema.sql b/internal/endtoend/testdata/invalid_table_alias/sqlite/schema.sql new file mode 100644 index 0000000000..fe5a44f601 --- /dev/null +++ b/internal/endtoend/testdata/invalid_table_alias/sqlite/schema.sql @@ -0,0 +1,6 @@ +-- https://github.com/sqlc-dev/sqlc/issues/437 +CREATE TABLE authors ( + id INTEGER PRIMARY KEY, + name VARCHAR(255) NOT NULL, + bio text +); diff --git a/internal/endtoend/testdata/invalid_table_alias/sqlite/sqlc.json b/internal/endtoend/testdata/invalid_table_alias/sqlite/sqlc.json index fcb288cb35..d4963e751f 100644 --- a/internal/endtoend/testdata/invalid_table_alias/sqlite/sqlc.json +++ b/internal/endtoend/testdata/invalid_table_alias/sqlite/sqlc.json @@ -5,7 +5,7 @@ "path": "go", "engine": "sqlite", "name": "querytest", - "schema": "query.sql", + "schema": "schema.sql", "queries": "query.sql" } ] diff --git a/internal/endtoend/testdata/invalid_table_alias/sqlite/stderr.txt b/internal/endtoend/testdata/invalid_table_alias/sqlite/stderr.txt index 810c893a70..97e43851e0 100644 --- a/internal/endtoend/testdata/invalid_table_alias/sqlite/stderr.txt +++ b/internal/endtoend/testdata/invalid_table_alias/sqlite/stderr.txt @@ -1,2 +1,2 @@ # package querytest -query.sql:11:9: table alias "p" does not exist +query.sql:1:1: sqlite3: SQL logic error: no such column: p.id diff --git a/internal/endtoend/testdata/join_left_same_table/sqlite/go/query.sql.go b/internal/endtoend/testdata/join_left_same_table/sqlite/go/query.sql.go index 82a6d25562..c25e22e249 100644 --- a/internal/endtoend/testdata/join_left_same_table/sqlite/go/query.sql.go +++ b/internal/endtoend/testdata/join_left_same_table/sqlite/go/query.sql.go @@ -17,7 +17,7 @@ SELECT a.id, p.name as alias_name FROM authors AS a LEFT JOIN authors AS p - ON (authors.parent_id = p.id) + ON (a.parent_id = p.id) ` type AllAuthorsRow struct { diff --git a/internal/endtoend/testdata/join_left_same_table/sqlite/query.sql b/internal/endtoend/testdata/join_left_same_table/sqlite/query.sql index 11f6c6903b..79daa2dfd5 100644 --- a/internal/endtoend/testdata/join_left_same_table/sqlite/query.sql +++ b/internal/endtoend/testdata/join_left_same_table/sqlite/query.sql @@ -5,4 +5,4 @@ SELECT a.id, p.name as alias_name FROM authors AS a LEFT JOIN authors AS p - ON (authors.parent_id = p.id); + ON (a.parent_id = p.id); diff --git a/internal/endtoend/testdata/limit/sqlite/go/query.sql.go b/internal/endtoend/testdata/limit/sqlite/go/query.sql.go index 31a0ab2993..3612dc12ef 100644 --- a/internal/endtoend/testdata/limit/sqlite/go/query.sql.go +++ b/internal/endtoend/testdata/limit/sqlite/go/query.sql.go @@ -9,15 +9,6 @@ import ( "context" ) -const deleteLimit = `-- name: DeleteLimit :exec -DELETE FROM foo LIMIT ? -` - -func (q *Queries) DeleteLimit(ctx context.Context, limit int64) error { - _, err := q.db.ExecContext(ctx, deleteLimit, limit) - return err -} - const limitMe = `-- name: LimitMe :many SELECT bar FROM foo LIMIT ? ` @@ -44,12 +35,3 @@ func (q *Queries) LimitMe(ctx context.Context, limit int64) ([]bool, error) { } return items, nil } - -const updateLimit = `-- name: UpdateLimit :exec -UPDATE foo SET bar='baz' LIMIT ? -` - -func (q *Queries) UpdateLimit(ctx context.Context, limit int64) error { - _, err := q.db.ExecContext(ctx, updateLimit, limit) - return err -} diff --git a/internal/endtoend/testdata/limit/sqlite/query.sql b/internal/endtoend/testdata/limit/sqlite/query.sql index 025e2a812b..8514c9b476 100644 --- a/internal/endtoend/testdata/limit/sqlite/query.sql +++ b/internal/endtoend/testdata/limit/sqlite/query.sql @@ -1,8 +1,2 @@ -- name: LimitMe :many SELECT bar FROM foo LIMIT ?; - --- name: UpdateLimit :exec -UPDATE foo SET bar='baz' LIMIT ?; - --- name: DeleteLimit :exec -DELETE FROM foo LIMIT ?; diff --git a/internal/endtoend/testdata/quoted_names_complex/sqlite/schema.sql b/internal/endtoend/testdata/quoted_names_complex/sqlite/schema.sql index fc6a73756e..5486831199 100644 --- a/internal/endtoend/testdata/quoted_names_complex/sqlite/schema.sql +++ b/internal/endtoend/testdata/quoted_names_complex/sqlite/schema.sql @@ -12,7 +12,7 @@ ALTER TABLE products ADD COLUMN "Price Info" text; -- Test mixed case operations across different statement types INSERT INTO "user profiles" ("profile data") VALUES ('test data'); -UPDATE "ORDERS" SET data = 'updated' WHERE id = 1; +UPDATE "customer_orders" SET data = 'updated' WHERE id = 1; DELETE FROM products WHERE id = 1; -- Test DROP with various identifier formats diff --git a/internal/endtoend/testdata/select_exists/sqlite/schema.sql b/internal/endtoend/testdata/select_exists/sqlite/schema.sql index 52799a37db..cf6a8b9507 100644 --- a/internal/endtoend/testdata/select_exists/sqlite/schema.sql +++ b/internal/endtoend/testdata/select_exists/sqlite/schema.sql @@ -1,2 +1 @@ -CREATE TABLE bar (id int not null primary key autoincrement); - +CREATE TABLE bar (id integer not null primary key autoincrement); diff --git a/internal/endtoend/testdata/select_not_exists/sqlite/exec.json b/internal/endtoend/testdata/select_not_exists/sqlite/exec.json new file mode 100644 index 0000000000..e5dfda7818 --- /dev/null +++ b/internal/endtoend/testdata/select_not_exists/sqlite/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["managed-db"] +} diff --git a/internal/endtoend/testdata/select_not_exists/sqlite/go/query.sql.go b/internal/endtoend/testdata/select_not_exists/sqlite/go/query.sql.go index ee1b8e548b..6da4636da8 100644 --- a/internal/endtoend/testdata/select_not_exists/sqlite/go/query.sql.go +++ b/internal/endtoend/testdata/select_not_exists/sqlite/go/query.sql.go @@ -10,7 +10,7 @@ import ( ) const barNotExists = `-- name: BarNotExists :one -SELECT +SELECT NOT EXISTS ( SELECT 1 @@ -21,8 +21,8 @@ SELECT ) ` -func (q *Queries) BarNotExists(ctx context.Context) (interface{}, error) { - row := q.db.QueryRowContext(ctx, barNotExists) +func (q *Queries) BarNotExists(ctx context.Context, dollar_1 interface{}) (interface{}, error) { + row := q.db.QueryRowContext(ctx, barNotExists, dollar_1) var column_1 interface{} err := row.Scan(&column_1) return column_1, err diff --git a/internal/endtoend/testdata/select_not_exists/sqlite/query.sql b/internal/endtoend/testdata/select_not_exists/sqlite/query.sql index d868c64a0b..f7e76ae92c 100644 --- a/internal/endtoend/testdata/select_not_exists/sqlite/query.sql +++ b/internal/endtoend/testdata/select_not_exists/sqlite/query.sql @@ -1,5 +1,5 @@ -- name: BarNotExists :one -SELECT +SELECT NOT EXISTS ( SELECT 1 diff --git a/internal/endtoend/testdata/sqlc_embed/sqlite/go/query.sql.go b/internal/endtoend/testdata/sqlc_embed/sqlite/go/query.sql.go index aafc0897a8..6b7b33ae28 100644 --- a/internal/endtoend/testdata/sqlc_embed/sqlite/go/query.sql.go +++ b/internal/endtoend/testdata/sqlc_embed/sqlite/go/query.sql.go @@ -35,7 +35,7 @@ func (q *Queries) Duplicate(ctx context.Context) (DuplicateRow, error) { const join = `-- name: Join :one SELECT u.id, u.name, u.age, p.id, p.user_id FROM posts AS p -INNER JOIN users AS u ON p.user_id = u.users.id +INNER JOIN users AS u ON p.user_id = u.id ` type JoinRow struct { diff --git a/internal/endtoend/testdata/sqlc_embed/sqlite/query.sql b/internal/endtoend/testdata/sqlc_embed/sqlite/query.sql index 4b999b5629..1d0a02f109 100644 --- a/internal/endtoend/testdata/sqlc_embed/sqlite/query.sql +++ b/internal/endtoend/testdata/sqlc_embed/sqlite/query.sql @@ -15,7 +15,7 @@ SELECT sqlc.embed(users), sqlc.embed(users) FROM users; -- name: Join :one SELECT sqlc.embed(u), sqlc.embed(p) FROM posts AS p -INNER JOIN users AS u ON p.user_id = u.users.id; +INNER JOIN users AS u ON p.user_id = u.id; -- name: WithSchema :one SELECT sqlc.embed(bu) FROM baz.users AS bu; diff --git a/internal/endtoend/testdata/sqlc_embed/sqlite/schema.sql b/internal/endtoend/testdata/sqlc_embed/sqlite/schema.sql index a67026ba33..5a1d371b7e 100644 --- a/internal/endtoend/testdata/sqlc_embed/sqlite/schema.sql +++ b/internal/endtoend/testdata/sqlc_embed/sqlite/schema.sql @@ -1,4 +1,4 @@ -ATTACH 'baz.db' AS baz; +ATTACH ':memory:' AS baz; CREATE TABLE users ( id integer PRIMARY KEY,