Skip to content

Commit 49e1ce4

Browse files
kyleconroyclaude
andcommitted
fix(sqlite): Handle in-memory databases and preserve inferred types
- 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 <[email protected]>
1 parent 8956d53 commit 49e1ce4

File tree

3 files changed

+65
-9
lines changed

3 files changed

+65
-9
lines changed

internal/cmd/vet.go

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,19 @@ type checker struct {
391391
Replacer *shfmt.Replacer
392392
}
393393

394+
// isInMemorySQLite checks if a SQLite URI refers to an in-memory database
395+
func isInMemorySQLite(uri string) bool {
396+
if uri == ":memory:" || uri == "" {
397+
return true
398+
}
399+
// Check for file URI with mode=memory parameter
400+
// e.g., "file:test?mode=memory&cache=shared"
401+
if strings.Contains(uri, "mode=memory") {
402+
return true
403+
}
404+
return false
405+
}
406+
394407
func (c *checker) fetchDatabaseUri(ctx context.Context, s config.SQL) (string, func() error, error) {
395408
cleanup := func() error {
396409
return nil
@@ -517,14 +530,31 @@ func (c *checker) checkSQL(ctx context.Context, s config.SQL) error {
517530
prep = &dbPreparer{db}
518531
expl = &mysqlExplainer{db}
519532
case config.EngineSQLite:
520-
db, err := sql.Open("sqlite", dburl)
533+
db, err := sql.Open("sqlite3", dburl)
521534
if err != nil {
522535
return fmt.Errorf("database: connection error: %s", err)
523536
}
524537
if err := db.PingContext(ctx); err != nil {
525538
return fmt.Errorf("database: connection error: %s", err)
526539
}
527540
defer db.Close()
541+
// For in-memory SQLite databases, apply migrations
542+
if isInMemorySQLite(dburl) {
543+
files, err := sqlpath.Glob(s.Schema)
544+
if err != nil {
545+
return fmt.Errorf("schema: %w", err)
546+
}
547+
for _, schema := range files {
548+
contents, err := os.ReadFile(schema)
549+
if err != nil {
550+
return fmt.Errorf("read schema file: %w", err)
551+
}
552+
ddl := migrations.RemoveRollbackStatements(string(contents))
553+
if _, err := db.ExecContext(ctx, ddl); err != nil {
554+
return fmt.Errorf("apply schema %s: %w", schema, err)
555+
}
556+
}
557+
}
528558
prep = &dbPreparer{db}
529559
// SQLite really doesn't want us to depend on the output of EXPLAIN
530560
// QUERY PLAN: https://www.sqlite.org/eqp.html

internal/compiler/analyze.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,13 @@ func combineAnalysis(prev *analysis, a *analyzer.Analysis) *analysis {
7979
}
8080
if len(prev.Columns) == len(cols) {
8181
for i := range prev.Columns {
82-
prev.Columns[i].DataType = cols[i].DataType
83-
prev.Columns[i].IsArray = cols[i].IsArray
84-
prev.Columns[i].ArrayDims = cols[i].ArrayDims
82+
// Only override column types if the analyzer provides a specific type
83+
// (not "any"), since the catalog-based inference may have better info
84+
if cols[i].DataType != "any" {
85+
prev.Columns[i].DataType = cols[i].DataType
86+
prev.Columns[i].IsArray = cols[i].IsArray
87+
prev.Columns[i].ArrayDims = cols[i].ArrayDims
88+
}
8589
}
8690
} else {
8791
embedding := false
@@ -96,9 +100,13 @@ func combineAnalysis(prev *analysis, a *analyzer.Analysis) *analysis {
96100
}
97101
if len(prev.Parameters) == len(params) {
98102
for i := range prev.Parameters {
99-
prev.Parameters[i].Column.DataType = params[i].Column.DataType
100-
prev.Parameters[i].Column.IsArray = params[i].Column.IsArray
101-
prev.Parameters[i].Column.ArrayDims = params[i].Column.ArrayDims
103+
// Only override parameter types if the analyzer provides a specific type
104+
// (not "any"), since the catalog-based inference may have better info
105+
if params[i].Column.DataType != "any" {
106+
prev.Parameters[i].Column.DataType = params[i].Column.DataType
107+
prev.Parameters[i].Column.IsArray = params[i].Column.IsArray
108+
prev.Parameters[i].Column.ArrayDims = params[i].Column.ArrayDims
109+
}
102110
}
103111
} else {
104112
prev.Parameters = params

internal/engine/sqlite/analyzer/analyze.go

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,18 @@ func (a *Analyzer) Analyze(ctx context.Context, n ast.Node, query string, migrat
4040

4141
if a.conn == nil {
4242
var uri string
43+
applyMigrations := a.db.Managed
4344
if a.db.Managed {
4445
// For managed databases, create an in-memory database
4546
uri = ":memory:"
4647
} else if a.dbg.OnlyManagedDatabases {
4748
return nil, fmt.Errorf("database: connections disabled via SQLCDEBUG=databases=managed")
4849
} else {
4950
uri = a.replacer.Replace(a.db.URI)
51+
// For in-memory databases, we need to apply migrations since the database starts empty
52+
if isInMemoryDatabase(uri) {
53+
applyMigrations = true
54+
}
5055
}
5156

5257
conn, err := sqlite3.Open(uri)
@@ -55,8 +60,8 @@ func (a *Analyzer) Analyze(ctx context.Context, n ast.Node, query string, migrat
5560
}
5661
a.conn = conn
5762

58-
// Apply migrations for managed databases
59-
if a.db.Managed {
63+
// Apply migrations for managed or in-memory databases
64+
if applyMigrations {
6065
for _, m := range migrations {
6166
if len(strings.TrimSpace(m)) == 0 {
6267
continue
@@ -177,6 +182,19 @@ func (a *Analyzer) Close(_ context.Context) error {
177182
return nil
178183
}
179184

185+
// isInMemoryDatabase checks if a SQLite URI refers to an in-memory database
186+
func isInMemoryDatabase(uri string) bool {
187+
if uri == ":memory:" || uri == "" {
188+
return true
189+
}
190+
// Check for file URI with mode=memory parameter
191+
// e.g., "file:test?mode=memory&cache=shared"
192+
if strings.Contains(uri, "mode=memory") {
193+
return true
194+
}
195+
return false
196+
}
197+
180198
// normalizeType converts SQLite type declarations to standard type names
181199
func normalizeType(declType string) string {
182200
if declType == "" {

0 commit comments

Comments
 (0)