fix panic when querying input with no SQL (only comments/whitespace)

sqlite3_prepare_v2 returns SQLITE_OK with a NULL statement handle when
the input contains no SQL. exec() already handled this; query() forwarded
the NULL handle to bind(), which crashed in sqlite3_clear_bindings(NULL).

Make query() skip NULL statements like exec() does, and make SQLiteRows
safe against a nil underlying statement so the empty-rows return value
does not crash.

Closes #1390
This commit is contained in:
Yasuhiro Matsumoto
2026-04-29 15:54:05 +09:00
parent 58e032d79a
commit 869e516d63
2 changed files with 61 additions and 6 deletions

View File

@@ -1017,25 +1017,37 @@ func (c *SQLiteConn) query(ctx context.Context, query string, args []driver.Name
if err != nil { if err != nil {
return nil, err return nil, err
} }
s.(*SQLiteStmt).cls = true ss := s.(*SQLiteStmt)
ss.cls = true
// sqlite3_prepare_v2 returns SQLITE_OK with a NULL statement handle
// when the input is empty or contains only whitespace/comments.
if ss.s == nil {
tail := ss.t
ss.Close()
if tail == "" {
return &SQLiteRows{cls: true, ctx: ctx}, nil
}
query = tail
continue
}
na := s.NumInput() na := s.NumInput()
if len(args)-start < na { if len(args)-start < na {
s.Close() ss.Close()
return nil, fmt.Errorf("not enough args to execute query: want %d got %d", na, len(args)-start) return nil, fmt.Errorf("not enough args to execute query: want %d got %d", na, len(args)-start)
} }
stmtArgs := stmtArgs(args, start, na) stmtArgs := stmtArgs(args, start, na)
rows, err := s.(*SQLiteStmt).query(ctx, stmtArgs) rows, err := ss.query(ctx, stmtArgs)
if err != nil && err != driver.ErrSkip { if err != nil && err != driver.ErrSkip {
s.Close() ss.Close()
return rows, err return rows, err
} }
start += na start += na
tail := s.(*SQLiteStmt).t tail := ss.t
if tail == "" { if tail == "" {
return rows, nil return rows, nil
} }
rows.Close() rows.Close()
s.Close() ss.Close()
query = tail query = tail
} }
} }
@@ -2441,6 +2453,9 @@ func (rc *SQLiteRows) Close() error {
// Columns return column names. // Columns return column names.
func (rc *SQLiteRows) Columns() []string { func (rc *SQLiteRows) Columns() []string {
if rc.s == nil {
return rc.cols
}
rc.s.mu.Lock() rc.s.mu.Lock()
defer rc.s.mu.Unlock() defer rc.s.mu.Unlock()
if rc.s.s != nil && int(rc.nc) != len(rc.cols) { if rc.s.s != nil && int(rc.nc) != len(rc.cols) {
@@ -2464,6 +2479,9 @@ func (rc *SQLiteRows) declTypes() []string {
// DeclTypes return column types. // DeclTypes return column types.
func (rc *SQLiteRows) DeclTypes() []string { func (rc *SQLiteRows) DeclTypes() []string {
if rc.s == nil {
return rc.decltype
}
rc.s.mu.Lock() rc.s.mu.Lock()
defer rc.s.mu.Unlock() defer rc.s.mu.Unlock()
return rc.declTypes() return rc.declTypes()
@@ -2471,6 +2489,9 @@ func (rc *SQLiteRows) DeclTypes() []string {
// Next move cursor to next. Attempts to honor context timeout from QueryContext call. // Next move cursor to next. Attempts to honor context timeout from QueryContext call.
func (rc *SQLiteRows) Next(dest []driver.Value) error { func (rc *SQLiteRows) Next(dest []driver.Value) error {
if rc.s == nil {
return io.EOF
}
rc.s.mu.Lock() rc.s.mu.Lock()
defer rc.s.mu.Unlock() defer rc.s.mu.Unlock()

View File

@@ -2065,6 +2065,40 @@ func TestNamedParamClearBindings(t *testing.T) {
} }
} }
// https://github.com/mattn/go-sqlite3/issues/1390
// sqlite3_prepare_v2 returns SQLITE_OK with a NULL statement handle when the
// input contains no SQL (only whitespace or comments). Querying such input
// must not panic.
func TestQueryCommentOnly(t *testing.T) {
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatal(err)
}
defer db.Close()
cases := []string{"", " ", "-- comment", "---- comment\n", "/* block */"}
for _, q := range cases {
var x int
if err := db.QueryRow(q).Scan(&x); err != sql.ErrNoRows {
t.Errorf("QueryRow(%q): expected ErrNoRows, got %v", q, err)
}
rows, err := db.Query(q)
if err != nil {
t.Errorf("Query(%q): unexpected error: %v", q, err)
continue
}
if rows.Next() {
t.Errorf("Query(%q): expected no rows", q)
}
rows.Close()
if _, err := db.Exec(q); err != nil {
t.Errorf("Exec(%q): unexpected error: %v", q, err)
}
}
}
var customFunctionOnce sync.Once var customFunctionOnce sync.Once
func BenchmarkCustomFunctions(b *testing.B) { func BenchmarkCustomFunctions(b *testing.B) {