diff --git a/sqlite3.go b/sqlite3.go index 1a5433c..d59541b 100644 --- a/sqlite3.go +++ b/sqlite3.go @@ -1017,25 +1017,37 @@ func (c *SQLiteConn) query(ctx context.Context, query string, args []driver.Name if err != nil { 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() 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) } stmtArgs := stmtArgs(args, start, na) - rows, err := s.(*SQLiteStmt).query(ctx, stmtArgs) + rows, err := ss.query(ctx, stmtArgs) if err != nil && err != driver.ErrSkip { - s.Close() + ss.Close() return rows, err } start += na - tail := s.(*SQLiteStmt).t + tail := ss.t if tail == "" { return rows, nil } rows.Close() - s.Close() + ss.Close() query = tail } } @@ -2441,6 +2453,9 @@ func (rc *SQLiteRows) Close() error { // Columns return column names. func (rc *SQLiteRows) Columns() []string { + if rc.s == nil { + return rc.cols + } rc.s.mu.Lock() defer rc.s.mu.Unlock() if rc.s.s != nil && int(rc.nc) != len(rc.cols) { @@ -2464,6 +2479,9 @@ func (rc *SQLiteRows) declTypes() []string { // DeclTypes return column types. func (rc *SQLiteRows) DeclTypes() []string { + if rc.s == nil { + return rc.decltype + } rc.s.mu.Lock() defer rc.s.mu.Unlock() 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. func (rc *SQLiteRows) Next(dest []driver.Value) error { + if rc.s == nil { + return io.EOF + } rc.s.mu.Lock() defer rc.s.mu.Unlock() diff --git a/sqlite3_test.go b/sqlite3_test.go index 852aa10..ac81458 100644 --- a/sqlite3_test.go +++ b/sqlite3_test.go @@ -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 func BenchmarkCustomFunctions(b *testing.B) {