Skip to content

Commit ffc490b

Browse files
kyleconroyclaude
andcommitted
refactor(expander): use native ncruces/go-sqlite3 API for column names
Use the native sqlite3.Conn.Prepare and stmt.ColumnName/ColumnCount APIs to get column names without executing the query. This is more efficient and consistent with how PostgreSQL handles it. Changes: - Add SQLiteColumnGetter using native sqlite3.Conn - Rename SQLColumnGetter to MySQLColumnGetter (MySQL still needs to execute) - SQLite test now uses sqlite3.Open instead of sql.Open 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3c31da6 commit ffc490b

File tree

1 file changed

+36
-13
lines changed

1 file changed

+36
-13
lines changed

internal/x/expander/expander_test.go

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
_ "github.com/go-sql-driver/mysql"
1111
"github.com/jackc/pgx/v5/pgxpool"
12-
_ "github.com/ncruces/go-sqlite3/driver"
12+
"github.com/ncruces/go-sqlite3"
1313
_ "github.com/ncruces/go-sqlite3/embed"
1414

1515
"github.com/sqlc-dev/sqlc/internal/engine/dolphin"
@@ -42,21 +42,21 @@ func (g *PostgreSQLColumnGetter) GetColumnNames(ctx context.Context, query strin
4242
return columns, nil
4343
}
4444

45-
// SQLColumnGetter implements ColumnGetter for MySQL and SQLite using database/sql.
46-
type SQLColumnGetter struct {
45+
// MySQLColumnGetter implements ColumnGetter for MySQL using database/sql.
46+
type MySQLColumnGetter struct {
4747
db *sql.DB
4848
}
4949

50-
func (g *SQLColumnGetter) GetColumnNames(ctx context.Context, query string) ([]string, error) {
50+
func (g *MySQLColumnGetter) GetColumnNames(ctx context.Context, query string) ([]string, error) {
5151
// Prepare the statement to validate the query and get column metadata
5252
stmt, err := g.db.PrepareContext(ctx, query)
5353
if err != nil {
5454
return nil, err
5555
}
5656
defer stmt.Close()
5757

58-
// Execute with LIMIT 0 workaround by wrapping in a subquery to get column names
59-
// without fetching actual data. We need to execute to get column metadata from database/sql.
58+
// Execute to get column metadata from database/sql.
59+
// database/sql doesn't expose column names from prepared statements directly.
6060
rows, err := stmt.QueryContext(ctx)
6161
if err != nil {
6262
return nil, err
@@ -66,6 +66,29 @@ func (g *SQLColumnGetter) GetColumnNames(ctx context.Context, query string) ([]s
6666
return rows.Columns()
6767
}
6868

69+
// SQLiteColumnGetter implements ColumnGetter for SQLite using the native ncruces/go-sqlite3 API.
70+
type SQLiteColumnGetter struct {
71+
conn *sqlite3.Conn
72+
}
73+
74+
func (g *SQLiteColumnGetter) GetColumnNames(ctx context.Context, query string) ([]string, error) {
75+
// Prepare the statement - this gives us column metadata without executing
76+
stmt, _, err := g.conn.Prepare(query)
77+
if err != nil {
78+
return nil, err
79+
}
80+
defer stmt.Close()
81+
82+
// Get column names from the prepared statement
83+
count := stmt.ColumnCount()
84+
columns := make([]string, count)
85+
for i := 0; i < count; i++ {
86+
columns[i] = stmt.ColumnName(i)
87+
}
88+
89+
return columns, nil
90+
}
91+
6992
func TestExpandPostgreSQL(t *testing.T) {
7093
// Skip if no database connection available
7194
uri := os.Getenv("POSTGRESQL_SERVER_URI")
@@ -251,7 +274,7 @@ func TestExpandMySQL(t *testing.T) {
251274
parser := dolphin.NewParser()
252275

253276
// Create the expander
254-
colGetter := &SQLColumnGetter{db: db}
277+
colGetter := &MySQLColumnGetter{db: db}
255278
exp := New(colGetter, parser, parser)
256279

257280
tests := []struct {
@@ -317,15 +340,15 @@ func TestExpandMySQL(t *testing.T) {
317340
func TestExpandSQLite(t *testing.T) {
318341
ctx := context.Background()
319342

320-
// Create an in-memory SQLite database
321-
db, err := sql.Open("sqlite3", ":memory:")
343+
// Create an in-memory SQLite database using native API
344+
conn, err := sqlite3.Open(":memory:")
322345
if err != nil {
323346
t.Fatalf("could not open SQLite: %v", err)
324347
}
325-
defer db.Close()
348+
defer conn.Close()
326349

327350
// Create a test table
328-
_, err = db.ExecContext(ctx, `
351+
err = conn.Exec(`
329352
CREATE TABLE authors (
330353
id INTEGER PRIMARY KEY AUTOINCREMENT,
331354
name TEXT NOT NULL,
@@ -339,8 +362,8 @@ func TestExpandSQLite(t *testing.T) {
339362
// Create the parser which also implements format.Dialect
340363
parser := sqlite.NewParser()
341364

342-
// Create the expander
343-
colGetter := &SQLColumnGetter{db: db}
365+
// Create the expander using native SQLite column getter
366+
colGetter := &SQLiteColumnGetter{conn: conn}
344367
exp := New(colGetter, parser, parser)
345368

346369
tests := []struct {

0 commit comments

Comments
 (0)