Skip to content

Commit 9c0681a

Browse files
committed
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 <[email protected]>
1 parent b807fe9 commit 9c0681a

File tree

6 files changed

+371
-6
lines changed

6 files changed

+371
-6
lines changed

go.mod

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ require (
2121
github.com/riza-io/grpc-go v0.2.0
2222
github.com/spf13/cobra v1.10.1
2323
github.com/spf13/pflag v1.0.10
24-
github.com/tetratelabs/wazero v1.9.0
24+
github.com/tetratelabs/wazero v1.10.1
2525
github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07
2626
github.com/xeipuuv/gojsonschema v1.2.0
27-
golang.org/x/sync v0.17.0
27+
golang.org/x/sync v0.18.0
2828
google.golang.org/grpc v1.76.0
2929
google.golang.org/protobuf v1.36.10
3030
gopkg.in/yaml.v3 v3.0.1
@@ -46,7 +46,9 @@ require (
4646
github.com/jackc/pgtype v1.14.0 // indirect
4747
github.com/jackc/puddle/v2 v2.2.2 // indirect
4848
github.com/mattn/go-isatty v0.0.20 // indirect
49+
github.com/ncruces/go-sqlite3 v0.30.2 // indirect
4950
github.com/ncruces/go-strftime v0.1.9 // indirect
51+
github.com/ncruces/julianday v1.0.0 // indirect
5052
github.com/pingcap/errors v0.11.5-0.20240311024730-e056997136bb // indirect
5153
github.com/pingcap/failpoint v0.0.0-20240528011301-b51a646c7c86 // indirect
5254
github.com/pingcap/log v1.1.0 // indirect
@@ -59,11 +61,11 @@ require (
5961
go.uber.org/atomic v1.11.0 // indirect
6062
go.uber.org/multierr v1.11.0 // indirect
6163
go.uber.org/zap v1.27.0 // indirect
62-
golang.org/x/crypto v0.40.0 // indirect
64+
golang.org/x/crypto v0.45.0 // indirect
6365
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
64-
golang.org/x/net v0.42.0 // indirect
65-
golang.org/x/sys v0.36.0 // indirect
66-
golang.org/x/text v0.27.0 // indirect
66+
golang.org/x/net v0.47.0 // indirect
67+
golang.org/x/sys v0.38.0 // indirect
68+
golang.org/x/text v0.31.0 // indirect
6769
google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect
6870
google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect
6971
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect

go.sum

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,12 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
125125
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
126126
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
127127
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
128+
github.com/ncruces/go-sqlite3 v0.30.2 h1:1GVbHAkKAOwjJd3JYl8ldrYROudfZUOah7oXPD7VZbQ=
129+
github.com/ncruces/go-sqlite3 v0.30.2/go.mod h1:AxKu9sRxkludimFocbktlY6LiYSkxiI5gTA8r+os/Nw=
128130
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
129131
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
132+
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
133+
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
130134
github.com/pganalyze/pg_query_go/v6 v6.1.0 h1:jG5ZLhcVgL1FAw4C/0VNQaVmX1SUJx71wBGdtTtBvls=
131135
github.com/pganalyze/pg_query_go/v6 v6.1.0/go.mod h1:nvTHIuoud6e1SfrUaFwHqT0i4b5Nr+1rPWVds3B5+50=
132136
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
179183
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
180184
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
181185
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
186+
github.com/tetratelabs/wazero v1.10.1 h1:2DugeJf6VVk58KTPszlNfeeN8AhhpwcZqkJj2wwFuH8=
187+
github.com/tetratelabs/wazero v1.10.1/go.mod h1:DRm5twOQ5Gr1AoEdSi0CLjDQF1J9ZAuyqFIjl1KKfQU=
182188
github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 h1:mJdDDPblDfPe7z7go8Dvv1AJQDI3eQ/5xith3q2mFlo=
183189
github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07/go.mod h1:Ak17IJ037caFp4jpCw/iQQ7/W74Sqpb1YuKJU6HTKfM=
184190
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 h1:OvLBa8SqJnZ6P+mjlzc2K7PM22rRUPE1x32G9DTPrC4=
@@ -238,23 +244,30 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
238244
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
239245
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
240246
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
247+
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
248+
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
241249
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
242250
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
243251
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
244252
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
245253
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
246254
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
247255
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
256+
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
248257
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
249258
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
250259
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
251260
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
252261
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
253262
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
254263
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
264+
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
265+
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
255266
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
256267
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
257268
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
269+
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
270+
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
258271
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
259272
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
260273
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
270283
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
271284
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
272285
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
286+
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
287+
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
273288
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
274289
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
275290
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=
279294
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
280295
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
281296
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
297+
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
298+
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
282299
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
283300
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
284301
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
290307
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
291308
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
292309
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
310+
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
293311
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
294312
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
295313
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

internal/compiler/engine.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/sqlc-dev/sqlc/internal/engine/postgresql"
1212
pganalyze "github.com/sqlc-dev/sqlc/internal/engine/postgresql/analyzer"
1313
"github.com/sqlc-dev/sqlc/internal/engine/sqlite"
14+
sqliteanalyze "github.com/sqlc-dev/sqlc/internal/engine/sqlite/analyzer"
1415
"github.com/sqlc-dev/sqlc/internal/opts"
1516
"github.com/sqlc-dev/sqlc/internal/sql/catalog"
1617
)
@@ -41,6 +42,15 @@ func NewCompiler(conf config.SQL, combo config.CombinedSettings) (*Compiler, err
4142
c.parser = sqlite.NewParser()
4243
c.catalog = sqlite.NewCatalog()
4344
c.selector = newSQLiteSelector()
45+
if conf.Database != nil {
46+
if conf.Analyzer.Database == nil || *conf.Analyzer.Database {
47+
c.analyzer = analyzer.Cached(
48+
sqliteanalyze.New(*conf.Database),
49+
combo.Global,
50+
*conf.Database,
51+
)
52+
}
53+
}
4454
case config.EngineMySQL:
4555
c.parser = dolphin.NewParser()
4656
c.catalog = dolphin.NewCatalog()

internal/endtoend/endtoend_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ func TestReplay(t *testing.T) {
161161
c.SQL[i].Database = &config.Database{
162162
Managed: true,
163163
}
164+
case config.EngineSQLite:
165+
c.SQL[i].Database = &config.Database{
166+
Managed: true,
167+
}
164168
default:
165169
// pass
166170
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package analyzer
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
"sync"
8+
9+
"github.com/ncruces/go-sqlite3"
10+
_ "github.com/ncruces/go-sqlite3/embed"
11+
12+
core "github.com/sqlc-dev/sqlc/internal/analysis"
13+
"github.com/sqlc-dev/sqlc/internal/config"
14+
"github.com/sqlc-dev/sqlc/internal/opts"
15+
"github.com/sqlc-dev/sqlc/internal/shfmt"
16+
"github.com/sqlc-dev/sqlc/internal/sql/ast"
17+
"github.com/sqlc-dev/sqlc/internal/sql/named"
18+
"github.com/sqlc-dev/sqlc/internal/sql/sqlerr"
19+
)
20+
21+
type Analyzer struct {
22+
db config.Database
23+
conn *sqlite3.Conn
24+
dbg opts.Debug
25+
replacer *shfmt.Replacer
26+
mu sync.Mutex
27+
}
28+
29+
func New(db config.Database) *Analyzer {
30+
return &Analyzer{
31+
db: db,
32+
dbg: opts.DebugFromEnv(),
33+
replacer: shfmt.NewReplacer(nil),
34+
}
35+
}
36+
37+
func (a *Analyzer) Analyze(ctx context.Context, n ast.Node, query string, migrations []string, ps *named.ParamSet) (*core.Analysis, error) {
38+
a.mu.Lock()
39+
defer a.mu.Unlock()
40+
41+
if a.conn == nil {
42+
var uri string
43+
if a.db.Managed {
44+
// For managed databases, create an in-memory database
45+
uri = ":memory:"
46+
} else if a.dbg.OnlyManagedDatabases {
47+
return nil, fmt.Errorf("database: connections disabled via SQLCDEBUG=databases=managed")
48+
} else {
49+
uri = a.replacer.Replace(a.db.URI)
50+
}
51+
52+
conn, err := sqlite3.Open(uri)
53+
if err != nil {
54+
return nil, fmt.Errorf("failed to open sqlite database: %w", err)
55+
}
56+
a.conn = conn
57+
58+
// Apply migrations for managed databases
59+
if a.db.Managed {
60+
for _, m := range migrations {
61+
if len(strings.TrimSpace(m)) == 0 {
62+
continue
63+
}
64+
if err := a.conn.Exec(m); err != nil {
65+
a.conn.Close()
66+
a.conn = nil
67+
return nil, fmt.Errorf("migration failed: %s: %w", m, err)
68+
}
69+
}
70+
}
71+
}
72+
73+
// Prepare the statement to get column and parameter information
74+
stmt, _, err := a.conn.Prepare(query)
75+
if err != nil {
76+
return nil, a.extractSqlErr(n, err)
77+
}
78+
defer stmt.Close()
79+
80+
var result core.Analysis
81+
82+
// Get column information
83+
colCount := stmt.ColumnCount()
84+
for i := 0; i < colCount; i++ {
85+
name := stmt.ColumnName(i)
86+
declType := stmt.ColumnDeclType(i)
87+
tableName := stmt.ColumnTableName(i)
88+
originName := stmt.ColumnOriginName(i)
89+
dbName := stmt.ColumnDatabaseName(i)
90+
91+
// Normalize the data type
92+
dataType := normalizeType(declType)
93+
94+
// Determine if column is NOT NULL
95+
// SQLite doesn't provide this info directly from prepared statements,
96+
// so we default to nullable (false)
97+
notNull := false
98+
99+
col := &core.Column{
100+
Name: name,
101+
OriginalName: originName,
102+
DataType: dataType,
103+
NotNull: notNull,
104+
}
105+
106+
if tableName != "" {
107+
col.Table = &core.Identifier{
108+
Schema: dbName,
109+
Name: tableName,
110+
}
111+
}
112+
113+
result.Columns = append(result.Columns, col)
114+
}
115+
116+
// Get parameter information
117+
bindCount := stmt.BindCount()
118+
for i := 1; i <= bindCount; i++ {
119+
paramName := stmt.BindName(i)
120+
121+
// SQLite doesn't provide parameter types from prepared statements
122+
// We use "any" as the default type
123+
name := ""
124+
if paramName != "" {
125+
// Remove the prefix (?, :, @, $) from parameter names
126+
name = strings.TrimLeft(paramName, "?:@$")
127+
}
128+
if ps != nil {
129+
if n, ok := ps.NameFor(i); ok {
130+
name = n
131+
}
132+
}
133+
134+
result.Params = append(result.Params, &core.Parameter{
135+
Number: int32(i),
136+
Column: &core.Column{
137+
Name: name,
138+
DataType: "any",
139+
NotNull: false,
140+
},
141+
})
142+
}
143+
144+
return &result, nil
145+
}
146+
147+
func (a *Analyzer) extractSqlErr(n ast.Node, err error) error {
148+
if err == nil {
149+
return nil
150+
}
151+
// Try to extract SQLite error details
152+
var sqliteErr *sqlite3.Error
153+
if e, ok := err.(*sqlite3.Error); ok {
154+
sqliteErr = e
155+
}
156+
if sqliteErr != nil {
157+
return &sqlerr.Error{
158+
Code: fmt.Sprintf("%d", sqliteErr.Code()),
159+
Message: sqliteErr.Error(),
160+
Location: n.Pos(),
161+
}
162+
}
163+
return &sqlerr.Error{
164+
Message: err.Error(),
165+
Location: n.Pos(),
166+
}
167+
}
168+
169+
func (a *Analyzer) Close(_ context.Context) error {
170+
a.mu.Lock()
171+
defer a.mu.Unlock()
172+
if a.conn != nil {
173+
err := a.conn.Close()
174+
a.conn = nil
175+
return err
176+
}
177+
return nil
178+
}
179+
180+
// normalizeType converts SQLite type declarations to standard type names
181+
func normalizeType(declType string) string {
182+
if declType == "" {
183+
return "any"
184+
}
185+
186+
// Convert to lowercase for comparison
187+
lower := strings.ToLower(declType)
188+
189+
// SQLite type affinity rules (https://www.sqlite.org/datatype3.html)
190+
switch {
191+
case strings.Contains(lower, "int"):
192+
return "integer"
193+
case strings.Contains(lower, "char"),
194+
strings.Contains(lower, "clob"),
195+
strings.Contains(lower, "text"):
196+
return "text"
197+
case strings.Contains(lower, "blob"):
198+
return "blob"
199+
case strings.Contains(lower, "real"),
200+
strings.Contains(lower, "floa"),
201+
strings.Contains(lower, "doub"):
202+
return "real"
203+
case strings.Contains(lower, "bool"):
204+
return "boolean"
205+
case strings.Contains(lower, "date"),
206+
strings.Contains(lower, "time"):
207+
return "datetime"
208+
default:
209+
// Return as-is for numeric or other types
210+
return lower
211+
}
212+
}

0 commit comments

Comments
 (0)