Repository: sqlc-dev/sqlc
Pull request #3069 (2023-12-19 06:17:55)

feat(analyzer): Return zero values when encountering unexpected ast nodes
This allows users to run generate on any parsable query, even when sqlc doesn't understand it.

Closes #2377

andrewmbenton: I think we need to think this through a little more. The vet_failures test adjustment hints at the fact that we are now adding queries to the compiler result (and codegenrequest) that we weren't previously, because they were "unsupported". We are doing this even though the queries don't have any annotation, which is perhaps unexpected.


andrewmbenton: > What do you think about ignoring queries that don't have an annotation? I think that feels like the expected behavior.

Yeah I had implemented that initially, and I think it's correct. It will change a decent amount of test output though so I would do it in a separate PR.

I could also argue that this isn't that big of a change, since we must have been adding un-annotated queries to codegen requests all along, but only if they were "supported."

Patch:
diff --git a/internal/compiler/analyze.go b/internal/compiler/analyze.go
index 82a67fb6f..38d66fce1 100644
--- a/internal/compiler/analyze.go
+++ b/internal/compiler/analyze.go
@@ -135,10 +135,6 @@ func (c *Compiler) _analyzeQuery(raw *ast.RawStmt, query string, failfast bool)
 
 	var table *ast.TableName
 	switch n := raw.Stmt.(type) {
-	case *ast.CallStmt:
-	case *ast.SelectStmt:
-	case *ast.DeleteStmt:
-	case *ast.DoStmt:
 	case *ast.InsertStmt:
 		if err := check(validate.InsertStmt(n)); err != nil {
 			return nil, err
@@ -148,15 +144,6 @@ func (c *Compiler) _analyzeQuery(raw *ast.RawStmt, query string, failfast bool)
 		if err := check(err); err != nil {
 			return nil, err
 		}
-	case *ast.ListenStmt:
-	case *ast.NotifyStmt:
-	case *ast.TruncateStmt:
-	case *ast.UpdateStmt:
-	case *ast.RefreshMatViewStmt:
-	default:
-		if err := check(ErrUnsupportedStatementType); err != nil {
-			return nil, err
-		}
 	}
 
 	if err := check(validate.FuncCall(c.catalog, c.combo, raw)); err != nil {
diff --git a/internal/compiler/compile.go b/internal/compiler/compile.go
index 19b737257..84fbb20a3 100644
--- a/internal/compiler/compile.go
+++ b/internal/compiler/compile.go
@@ -79,9 +79,6 @@ func (c *Compiler) parseQueries(o opts.Parser) (*Result, error) {
 		}
 		for _, stmt := range stmts {
 			query, err := c.parseQuery(stmt.Raw, src, o)
-			if err == ErrUnsupportedStatementType {
-				continue
-			}
 			if err != nil {
 				var e *sqlerr.Error
 				loc := stmt.Raw.Pos()
@@ -95,6 +92,10 @@ func (c *Compiler) parseQueries(o opts.Parser) (*Result, error) {
 				}
 				continue
 			}
+			if query == nil {
+				continue
+			}
+			query.Metadata.Filename = filepath.Base(filename)
 			queryName := query.Metadata.Name
 			if queryName != "" {
 				if _, exists := set[queryName]; exists {
@@ -103,10 +104,7 @@ func (c *Compiler) parseQueries(o opts.Parser) (*Result, error) {
 				}
 				set[queryName] = struct{}{}
 			}
-			query.Metadata.Filename = filepath.Base(filename)
-			if query != nil {
-				q = append(q, query)
-			}
+			q = append(q, query)
 		}
 	}
 	if len(merr.Errs()) > 0 {
diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go
index 5b96a0856..dbdbe252b 100644
--- a/internal/compiler/output_columns.go
+++ b/internal/compiler/output_columns.go
@@ -57,7 +57,7 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er
 		return nil, err
 	}
 
-	var targets *ast.List
+	targets := &ast.List{}
 	switch n := node.(type) {
 	case *ast.DeleteStmt:
 		targets = n.ReturningList
@@ -114,16 +114,8 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er
 		if isUnion {
 			return c.outputColumns(qc, n.Larg)
 		}
-	case *ast.DoStmt:
-		targets = &ast.List{}
-	case *ast.CallStmt:
-		targets = &ast.List{}
-	case *ast.TruncateStmt, *ast.RefreshMatViewStmt, *ast.NotifyStmt, *ast.ListenStmt:
-		targets = &ast.List{}
 	case *ast.UpdateStmt:
 		targets = n.ReturningList
-	default:
-		return nil, fmt.Errorf("outputColumns: unsupported node type: %T", n)
 	}
 
 	var cols []*Column
@@ -487,7 +479,7 @@ func (r *tableVisitor) Visit(n ast.Node) astutils.Visitor {
 // Return an error if a table is referenced twice
 // Return an error if an unknown column is referenced
 func (c *Compiler) sourceTables(qc *QueryCatalog, node ast.Node) ([]*Table, error) {
-	var list *ast.List
+	list := &ast.List{}
 	switch n := node.(type) {
 	case *ast.DeleteStmt:
 		list = n.Relations
@@ -514,14 +506,6 @@ func (c *Compiler) sourceTables(qc *QueryCatalog, node ast.Node) ([]*Table, erro
 		astutils.Walk(&tv, n.FromClause)
 		astutils.Walk(&tv, n.Relations)
 		list = &tv.list
-	case *ast.DoStmt:
-		list = &ast.List{}
-	case *ast.CallStmt:
-		list = &ast.List{}
-	case *ast.NotifyStmt, *ast.ListenStmt:
-		list = &ast.List{}
-	default:
-		return nil, fmt.Errorf("sourceTables: unsupported node type: %T", n)
 	}
 
 	var tables []*Table
diff --git a/internal/compiler/parse.go b/internal/compiler/parse.go
index b47a72bee..8f4727144 100644
--- a/internal/compiler/parse.go
+++ b/internal/compiler/parse.go
@@ -15,8 +15,6 @@ import (
 	"github.com/sqlc-dev/sqlc/internal/sql/validate"
 )
 
-var ErrUnsupportedStatementType = errors.New("parseQuery: unsupported statement type")
-
 func (c *Compiler) parseQuery(stmt ast.Node, src string, o opts.Parser) (*Query, error) {
 	ctx := context.Background()
 


Test patch:
diff --git a/internal/endtoend/testdata/exec_create_table/mysql/db/db.go b/internal/endtoend/testdata/exec_create_table/mysql/db/db.go
new file mode 100644
index 000000000..e06d75a08
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/mysql/db/db.go
@@ -0,0 +1,31 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+
+package db
+
+import (
+	"context"
+	"database/sql"
+)
+
+type DBTX interface {
+	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
+	PrepareContext(context.Context, string) (*sql.Stmt, error)
+	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
+	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
+}
+
+func New(db DBTX) *Queries {
+	return &Queries{db: db}
+}
+
+type Queries struct {
+	db DBTX
+}
+
+func (q *Queries) WithTx(tx *sql.Tx) *Queries {
+	return &Queries{
+		db: tx,
+	}
+}
diff --git a/internal/endtoend/testdata/exec_create_table/mysql/db/models.go b/internal/endtoend/testdata/exec_create_table/mysql/db/models.go
new file mode 100644
index 000000000..477b93130
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/mysql/db/models.go
@@ -0,0 +1,7 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+
+package db
+
+import ()
diff --git a/internal/endtoend/testdata/exec_create_table/mysql/db/mysql.query.sql.go b/internal/endtoend/testdata/exec_create_table/mysql/db/mysql.query.sql.go
new file mode 100644
index 000000000..8dba113e5
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/mysql/db/mysql.query.sql.go
@@ -0,0 +1,19 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+// source: mysql.query.sql
+
+package db
+
+import (
+	"context"
+)
+
+const createTable = `-- name: CreateTable :exec
+CREATE TABLE test (id INTEGER NOT NULL)
+`
+
+func (q *Queries) CreateTable(ctx context.Context) error {
+	_, err := q.db.ExecContext(ctx, createTable)
+	return err
+}
diff --git a/internal/endtoend/testdata/exec_create_table/mysql/mysql.query.sql b/internal/endtoend/testdata/exec_create_table/mysql/mysql.query.sql
new file mode 100644
index 000000000..0a92ebb9c
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/mysql/mysql.query.sql
@@ -0,0 +1,2 @@
+-- name: CreateTable :exec
+CREATE TABLE test (id INTEGER NOT NULL);
diff --git a/internal/endtoend/testdata/exec_create_table/mysql/mysql.schema.sql b/internal/endtoend/testdata/exec_create_table/mysql/mysql.schema.sql
new file mode 100644
index 000000000..e69de29bb
diff --git a/internal/endtoend/testdata/exec_create_table/mysql/sqlc.yaml b/internal/endtoend/testdata/exec_create_table/mysql/sqlc.yaml
new file mode 100644
index 000000000..bd888aabf
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/mysql/sqlc.yaml
@@ -0,0 +1,8 @@
+version: 2
+sql:
+- queries: mysql.query.sql
+  schema: mysql.schema.sql
+  engine: mysql
+  gen:
+    go:
+      out: db
diff --git a/internal/endtoend/testdata/exec_create_table/postgresql/db/db.go b/internal/endtoend/testdata/exec_create_table/postgresql/db/db.go
new file mode 100644
index 000000000..e06d75a08
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/postgresql/db/db.go
@@ -0,0 +1,31 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+
+package db
+
+import (
+	"context"
+	"database/sql"
+)
+
+type DBTX interface {
+	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
+	PrepareContext(context.Context, string) (*sql.Stmt, error)
+	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
+	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
+}
+
+func New(db DBTX) *Queries {
+	return &Queries{db: db}
+}
+
+type Queries struct {
+	db DBTX
+}
+
+func (q *Queries) WithTx(tx *sql.Tx) *Queries {
+	return &Queries{
+		db: tx,
+	}
+}
diff --git a/internal/endtoend/testdata/exec_create_table/postgresql/db/models.go b/internal/endtoend/testdata/exec_create_table/postgresql/db/models.go
new file mode 100644
index 000000000..477b93130
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/postgresql/db/models.go
@@ -0,0 +1,7 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+
+package db
+
+import ()
diff --git a/internal/endtoend/testdata/exec_create_table/postgresql/db/postgresql.query.sql.go b/internal/endtoend/testdata/exec_create_table/postgresql/db/postgresql.query.sql.go
new file mode 100644
index 000000000..24e23cefe
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/postgresql/db/postgresql.query.sql.go
@@ -0,0 +1,19 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+// source: postgresql.query.sql
+
+package db
+
+import (
+	"context"
+)
+
+const createTable = `-- name: CreateTable :exec
+CREATE TABLE test (id INTEGER NOT NULL)
+`
+
+func (q *Queries) CreateTable(ctx context.Context) error {
+	_, err := q.db.ExecContext(ctx, createTable)
+	return err
+}
diff --git a/internal/endtoend/testdata/exec_create_table/postgresql/postgresql.query.sql b/internal/endtoend/testdata/exec_create_table/postgresql/postgresql.query.sql
new file mode 100644
index 000000000..0a92ebb9c
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/postgresql/postgresql.query.sql
@@ -0,0 +1,2 @@
+-- name: CreateTable :exec
+CREATE TABLE test (id INTEGER NOT NULL);
diff --git a/internal/endtoend/testdata/exec_create_table/postgresql/postgresql.schema.sql b/internal/endtoend/testdata/exec_create_table/postgresql/postgresql.schema.sql
new file mode 100644
index 000000000..e69de29bb
diff --git a/internal/endtoend/testdata/exec_create_table/postgresql/sqlc.yaml b/internal/endtoend/testdata/exec_create_table/postgresql/sqlc.yaml
new file mode 100644
index 000000000..6a89eb217
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/postgresql/sqlc.yaml
@@ -0,0 +1,9 @@
+version: 2
+sql:
+- queries: postgresql.query.sql
+  schema: postgresql.schema.sql
+  engine: postgresql
+  gen:
+    go:
+      out: db
+
diff --git a/internal/endtoend/testdata/exec_create_table/sqlite/db/db.go b/internal/endtoend/testdata/exec_create_table/sqlite/db/db.go
new file mode 100644
index 000000000..e06d75a08
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/sqlite/db/db.go
@@ -0,0 +1,31 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+
+package db
+
+import (
+	"context"
+	"database/sql"
+)
+
+type DBTX interface {
+	ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
+	PrepareContext(context.Context, string) (*sql.Stmt, error)
+	QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
+	QueryRowContext(context.Context, string, ...interface{}) *sql.Row
+}
+
+func New(db DBTX) *Queries {
+	return &Queries{db: db}
+}
+
+type Queries struct {
+	db DBTX
+}
+
+func (q *Queries) WithTx(tx *sql.Tx) *Queries {
+	return &Queries{
+		db: tx,
+	}
+}
diff --git a/internal/endtoend/testdata/exec_create_table/sqlite/db/models.go b/internal/endtoend/testdata/exec_create_table/sqlite/db/models.go
new file mode 100644
index 000000000..477b93130
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/sqlite/db/models.go
@@ -0,0 +1,7 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+
+package db
+
+import ()
diff --git a/internal/endtoend/testdata/exec_create_table/sqlite/db/sqlite.query.sql.go b/internal/endtoend/testdata/exec_create_table/sqlite/db/sqlite.query.sql.go
new file mode 100644
index 000000000..ea8fcafec
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/sqlite/db/sqlite.query.sql.go
@@ -0,0 +1,19 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.24.0
+// source: sqlite.query.sql
+
+package db
+
+import (
+	"context"
+)
+
+const createTable = `-- name: CreateTable :exec
+CREATE TABLE test (id INTEGER NOT NULL)
+`
+
+func (q *Queries) CreateTable(ctx context.Context) error {
+	_, err := q.db.ExecContext(ctx, createTable)
+	return err
+}
diff --git a/internal/endtoend/testdata/exec_create_table/sqlite/sqlc.yaml b/internal/endtoend/testdata/exec_create_table/sqlite/sqlc.yaml
new file mode 100644
index 000000000..bf2e6024b
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/sqlite/sqlc.yaml
@@ -0,0 +1,8 @@
+version: 2
+sql:
+- queries: sqlite.query.sql
+  schema: sqlite.schema.sql
+  engine: sqlite
+  gen:
+    go:
+      out: db
diff --git a/internal/endtoend/testdata/exec_create_table/sqlite/sqlite.query.sql b/internal/endtoend/testdata/exec_create_table/sqlite/sqlite.query.sql
new file mode 100644
index 000000000..0a92ebb9c
--- /dev/null
+++ b/internal/endtoend/testdata/exec_create_table/sqlite/sqlite.query.sql
@@ -0,0 +1,2 @@
+-- name: CreateTable :exec
+CREATE TABLE test (id INTEGER NOT NULL);
diff --git a/internal/endtoend/testdata/exec_create_table/sqlite/sqlite.schema.sql b/internal/endtoend/testdata/exec_create_table/sqlite/sqlite.schema.sql
new file mode 100644
index 000000000..e69de29bb
diff --git a/internal/endtoend/testdata/vet_failures/query.sql b/internal/endtoend/testdata/vet_failures/query.sql
index 718f3395c..75e38b2ca 100644
--- a/internal/endtoend/testdata/vet_failures/query.sql
+++ b/internal/endtoend/testdata/vet_failures/query.sql
@@ -1,9 +1,3 @@
-CREATE TABLE authors (
-          id   BIGSERIAL PRIMARY KEY,
-          name text      NOT NULL,
-          bio  text
-);
-
 -- name: GetAuthor :one
 SELECT * FROM authors
 WHERE id = $1 LIMIT 1;
diff --git a/internal/endtoend/testdata/vet_failures/sqlc.yaml b/internal/endtoend/testdata/vet_failures/sqlc.yaml
index 10d5246f8..b1cff78c3 100644
--- a/internal/endtoend/testdata/vet_failures/sqlc.yaml
+++ b/internal/endtoend/testdata/vet_failures/sqlc.yaml
@@ -1,6 +1,6 @@
 version: 2
 sql:
-  - schema: "query.sql"
+  - schema: "schema.sql"
     queries: "query.sql"
     engine: "postgresql"
     gen: