Repository: sqlc-dev/sqlc
Pull request #2852 (2023-10-13 17:45:43)

fix(mysql): Handle simplified CASE statements
MySQL and PostgreSQL now have the same behavior, generating a nullable string parameter. That behavior isn't right, as we'd expect the parameter to be a boolean. This can be fixed by using a type case or the new annotations in #2800.

Fixes https://github.com/sqlc-dev/sqlc/issues/2847


Patch:
diff --git a/internal/codegen/golang/query.go b/internal/codegen/golang/query.go
index a52c6249d..b82178686 100644
--- a/internal/codegen/golang/query.go
+++ b/internal/codegen/golang/query.go
@@ -61,7 +61,7 @@ func (v QueryValue) Pairs() []Argument {
 		var out []Argument
 		for _, f := range v.Struct.Fields {
 			out = append(out, Argument{
-				Name: toLowerCase(f.Name),
+				Name: escape(toLowerCase(f.Name)),
 				Type: f.Type,
 			})
 		}
@@ -69,7 +69,7 @@ func (v QueryValue) Pairs() []Argument {
 	}
 	return []Argument{
 		{
-			Name: v.Name,
+			Name: escape(v.Name),
 			Type: v.DefineType(),
 		},
 	}
@@ -102,9 +102,9 @@ func (v *QueryValue) DefineType() string {
 
 func (v *QueryValue) ReturnName() string {
 	if v.IsPointer() {
-		return "&" + v.Name
+		return "&" + escape(v.Name)
 	}
-	return v.Name
+	return escape(v.Name)
 }
 
 func (v QueryValue) UniqueFields() []Field {
@@ -129,16 +129,16 @@ func (v QueryValue) Params() string {
 	var out []string
 	if v.Struct == nil {
 		if !v.Column.IsSqlcSlice && strings.HasPrefix(v.Typ, "[]") && v.Typ != "[]byte" && !v.SQLDriver.IsPGX() {
-			out = append(out, "pq.Array("+v.Name+")")
+			out = append(out, "pq.Array("+escape(v.Name)+")")
 		} else {
-			out = append(out, v.Name)
+			out = append(out, escape(v.Name))
 		}
 	} else {
 		for _, f := range v.Struct.Fields {
 			if !f.HasSqlcSlice() && strings.HasPrefix(f.Type, "[]") && f.Type != "[]byte" && !v.SQLDriver.IsPGX() {
-				out = append(out, "pq.Array("+v.VariableForField(f)+")")
+				out = append(out, "pq.Array("+escape(v.VariableForField(f))+")")
 			} else {
-				out = append(out, v.VariableForField(f))
+				out = append(out, escape(v.VariableForField(f)))
 			}
 		}
 	}
diff --git a/internal/codegen/golang/reserved.go b/internal/codegen/golang/reserved.go
new file mode 100644
index 000000000..fee711c57
--- /dev/null
+++ b/internal/codegen/golang/reserved.go
@@ -0,0 +1,65 @@
+package golang
+
+func escape(s string) string {
+	if IsReserved(s) {
+		return s + "_"
+	}
+	return s
+}
+
+func IsReserved(s string) bool {
+	switch s {
+	case "break":
+		return true
+	case "default":
+		return true
+	case "func":
+		return true
+	case "interface":
+		return true
+	case "select":
+		return true
+	case "case":
+		return true
+	case "defer":
+		return true
+	case "go":
+		return true
+	case "map":
+		return true
+	case "struct":
+		return true
+	case "chan":
+		return true
+	case "else":
+		return true
+	case "goto":
+		return true
+	case "package":
+		return true
+	case "switch":
+		return true
+	case "const":
+		return true
+	case "fallthrough":
+		return true
+	case "if":
+		return true
+	case "range":
+		return true
+	case "type":
+		return true
+	case "continue":
+		return true
+	case "for":
+		return true
+	case "import":
+		return true
+	case "return":
+		return true
+	case "var":
+		return true
+	default:
+		return false
+	}
+}
diff --git a/internal/codegen/golang/result.go b/internal/codegen/golang/result.go
index 953882b14..03e2d21fc 100644
--- a/internal/codegen/golang/result.go
+++ b/internal/codegen/golang/result.go
@@ -215,7 +215,7 @@ func buildQueries(req *plugin.CodeGenRequest, options *opts, structs []Struct) (
 		if len(query.Params) == 1 && qpl != 0 {
 			p := query.Params[0]
 			gq.Arg = QueryValue{
-				Name:      paramName(p),
+				Name:      escape(paramName(p)),
 				DBName:    p.Column.GetName(),
 				Typ:       goType(req, options, p.Column),
 				SQLDriver: sqlpkg,
@@ -251,7 +251,7 @@ func buildQueries(req *plugin.CodeGenRequest, options *opts, structs []Struct) (
 			name := columnName(c, 0)
 			name = strings.Replace(name, "$", "_", -1)
 			gq.Ret = QueryValue{
-				Name:      name,
+				Name:      escape(name),
 				DBName:    name,
 				Typ:       goType(req, options, c),
 				SQLDriver: sqlpkg,
diff --git a/internal/engine/dolphin/convert.go b/internal/engine/dolphin/convert.go
index b16e0a2a3..b23758c4c 100644
--- a/internal/engine/dolphin/convert.go
+++ b/internal/engine/dolphin/convert.go
@@ -745,6 +745,7 @@ func (c *cc) convertCaseExpr(n *pcast.CaseExpr) ast.Node {
 		list.Items = append(list.Items, c.convertWhenClause(n))
 	}
 	return &ast.CaseExpr{
+		Arg:       c.convert(n.Value),
 		Args:      list,
 		Defresult: c.convert(n.ElseClause),
 		Location:  n.OriginTextPosition(),
diff --git a/internal/engine/postgresql/analyzer/analyze.go b/internal/engine/postgresql/analyzer/analyze.go
index 0a1271e80..ab34eb348 100644
--- a/internal/engine/postgresql/analyzer/analyze.go
+++ b/internal/engine/postgresql/analyzer/analyze.go
@@ -101,7 +101,6 @@ func (a *Analyzer) columnInfo(ctx context.Context, field pgconn.FieldDescription
 	if ok {
 		return cinfo.(*pgColumn), nil
 	}
-
 	rows, err := a.pool.Query(ctx, columnQuery, field.TableOID, field.TableAttributeNumber)
 	if err != nil {
 		return nil, err


Test patch:
diff --git a/internal/endtoend/testdata/case_value_param/issue.md b/internal/endtoend/testdata/case_value_param/issue.md
new file mode 100644
index 000000000..c8ac2fa39
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/issue.md
@@ -0,0 +1,1 @@
+https://github.com/sqlc-dev/sqlc/issues/2847
diff --git a/internal/endtoend/testdata/case_value_param/mysql/go/db.go b/internal/endtoend/testdata/case_value_param/mysql/go/db.go
new file mode 100644
index 000000000..a457fb76b
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/mysql/go/db.go
@@ -0,0 +1,31 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.22.0
+
+package querytest
+
+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/case_value_param/mysql/go/models.go b/internal/endtoend/testdata/case_value_param/mysql/go/models.go
new file mode 100644
index 000000000..03c6b31ce
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/mysql/go/models.go
@@ -0,0 +1,14 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.22.0
+
+package querytest
+
+import (
+	"database/sql"
+)
+
+type Testing struct {
+	ID    int32
+	Value sql.NullString
+}
diff --git a/internal/endtoend/testdata/case_value_param/mysql/go/query.sql.go b/internal/endtoend/testdata/case_value_param/mysql/go/query.sql.go
new file mode 100644
index 000000000..97eb3800f
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/mysql/go/query.sql.go
@@ -0,0 +1,21 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.22.0
+// source: query.sql
+
+package querytest
+
+import (
+	"context"
+	"database/sql"
+)
+
+const update = `-- name: Update :exec
+UPDATE testing
+SET value = CASE ? WHEN true THEN 'Hello' WHEN false THEN 'Goodbye' ELSE value END
+`
+
+func (q *Queries) Update(ctx context.Context, value sql.NullString) error {
+	_, err := q.db.ExecContext(ctx, update, value)
+	return err
+}
diff --git a/internal/endtoend/testdata/case_value_param/mysql/query.sql b/internal/endtoend/testdata/case_value_param/mysql/query.sql
new file mode 100644
index 000000000..2c4e1f988
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/mysql/query.sql
@@ -0,0 +1,3 @@
+-- name: Update :exec
+UPDATE testing
+SET value = CASE ? WHEN true THEN 'Hello' WHEN false THEN 'Goodbye' ELSE value END;
diff --git a/internal/endtoend/testdata/case_value_param/mysql/schema.sql b/internal/endtoend/testdata/case_value_param/mysql/schema.sql
new file mode 100644
index 000000000..556ca0b53
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/mysql/schema.sql
@@ -0,0 +1,4 @@
+CREATE TABLE testing (
+  id    int PRIMARY KEY,
+  value text
+);
diff --git a/internal/endtoend/testdata/case_value_param/mysql/sqlc.yaml b/internal/endtoend/testdata/case_value_param/mysql/sqlc.yaml
new file mode 100644
index 000000000..b843ef55e
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/mysql/sqlc.yaml
@@ -0,0 +1,9 @@
+version: "2"
+sql:
+  - engine: "mysql"
+    schema: "schema.sql"
+    queries: "query.sql"
+    gen:
+      go:
+        package: "querytest"
+        out: "go"
diff --git a/internal/endtoend/testdata/case_value_param/postgresql/go/db.go b/internal/endtoend/testdata/case_value_param/postgresql/go/db.go
new file mode 100644
index 000000000..8a010ccc4
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/postgresql/go/db.go
@@ -0,0 +1,32 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.22.0
+
+package querytest
+
+import (
+	"context"
+
+	"github.com/jackc/pgx/v5"
+	"github.com/jackc/pgx/v5/pgconn"
+)
+
+type DBTX interface {
+	Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
+	Query(context.Context, string, ...interface{}) (pgx.Rows, error)
+	QueryRow(context.Context, string, ...interface{}) pgx.Row
+}
+
+func New(db DBTX) *Queries {
+	return &Queries{db: db}
+}
+
+type Queries struct {
+	db DBTX
+}
+
+func (q *Queries) WithTx(tx pgx.Tx) *Queries {
+	return &Queries{
+		db: tx,
+	}
+}
diff --git a/internal/endtoend/testdata/case_value_param/postgresql/go/models.go b/internal/endtoend/testdata/case_value_param/postgresql/go/models.go
new file mode 100644
index 000000000..a613e3544
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/postgresql/go/models.go
@@ -0,0 +1,14 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.22.0
+
+package querytest
+
+import (
+	"github.com/jackc/pgx/v5/pgtype"
+)
+
+type Testing struct {
+	ID    int32
+	Value pgtype.Text
+}
diff --git a/internal/endtoend/testdata/case_value_param/postgresql/go/query.sql.go b/internal/endtoend/testdata/case_value_param/postgresql/go/query.sql.go
new file mode 100644
index 000000000..841cfb52b
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/postgresql/go/query.sql.go
@@ -0,0 +1,22 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.22.0
+// source: query.sql
+
+package querytest
+
+import (
+	"context"
+
+	"github.com/jackc/pgx/v5/pgtype"
+)
+
+const update = `-- name: Update :exec
+UPDATE testing
+SET value = CASE $1 WHEN true THEN 'Hello' WHEN false THEN 'Goodbye' ELSE value END
+`
+
+func (q *Queries) Update(ctx context.Context, value pgtype.Text) error {
+	_, err := q.db.Exec(ctx, update, value)
+	return err
+}
diff --git a/internal/endtoend/testdata/case_value_param/postgresql/query.sql b/internal/endtoend/testdata/case_value_param/postgresql/query.sql
new file mode 100644
index 000000000..106653b43
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/postgresql/query.sql
@@ -0,0 +1,3 @@
+-- name: Update :exec
+UPDATE testing
+SET value = CASE $1 WHEN true THEN 'Hello' WHEN false THEN 'Goodbye' ELSE value END;
diff --git a/internal/endtoend/testdata/case_value_param/postgresql/schema.sql b/internal/endtoend/testdata/case_value_param/postgresql/schema.sql
new file mode 100644
index 000000000..556ca0b53
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/postgresql/schema.sql
@@ -0,0 +1,4 @@
+CREATE TABLE testing (
+  id    int PRIMARY KEY,
+  value text
+);
diff --git a/internal/endtoend/testdata/case_value_param/postgresql/sqlc.yaml b/internal/endtoend/testdata/case_value_param/postgresql/sqlc.yaml
new file mode 100644
index 000000000..5dc63e3f9
--- /dev/null
+++ b/internal/endtoend/testdata/case_value_param/postgresql/sqlc.yaml
@@ -0,0 +1,10 @@
+version: "2"
+sql:
+  - engine: "postgresql"
+    schema: "schema.sql"
+    queries: "query.sql"
+    gen:
+      go:
+        package: "querytest"
+        out: "go"
+        sql_package: "pgx/v5"
diff --git a/internal/endtoend/testdata/params_go_keywords/issue.md b/internal/endtoend/testdata/params_go_keywords/issue.md
new file mode 100644
index 000000000..f87f5c14c
--- /dev/null
+++ b/internal/endtoend/testdata/params_go_keywords/issue.md
@@ -0,0 +1,1 @@
+# TODO
\ No newline at end of file
diff --git a/internal/endtoend/testdata/params_go_keywords/postgresql/go/db.go b/internal/endtoend/testdata/params_go_keywords/postgresql/go/db.go
new file mode 100644
index 000000000..8a010ccc4
--- /dev/null
+++ b/internal/endtoend/testdata/params_go_keywords/postgresql/go/db.go
@@ -0,0 +1,32 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.22.0
+
+package querytest
+
+import (
+	"context"
+
+	"github.com/jackc/pgx/v5"
+	"github.com/jackc/pgx/v5/pgconn"
+)
+
+type DBTX interface {
+	Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
+	Query(context.Context, string, ...interface{}) (pgx.Rows, error)
+	QueryRow(context.Context, string, ...interface{}) pgx.Row
+}
+
+func New(db DBTX) *Queries {
+	return &Queries{db: db}
+}
+
+type Queries struct {
+	db DBTX
+}
+
+func (q *Queries) WithTx(tx pgx.Tx) *Queries {
+	return &Queries{
+		db: tx,
+	}
+}
diff --git a/internal/endtoend/testdata/params_go_keywords/postgresql/go/models.go b/internal/endtoend/testdata/params_go_keywords/postgresql/go/models.go
new file mode 100644
index 000000000..e857924a3
--- /dev/null
+++ b/internal/endtoend/testdata/params_go_keywords/postgresql/go/models.go
@@ -0,0 +1,37 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.22.0
+
+package querytest
+
+import (
+	"github.com/jackc/pgx/v5/pgtype"
+)
+
+type GoKeyword struct {
+	Break       pgtype.Text
+	Default     pgtype.Text
+	Func        pgtype.Text
+	Interface   pgtype.Text
+	Select      pgtype.Text
+	Case        pgtype.Text
+	Defer       pgtype.Text
+	Go          pgtype.Text
+	Map         pgtype.Text
+	Struct      pgtype.Text
+	Chan        pgtype.Text
+	Else        pgtype.Text
+	Goto        pgtype.Text
+	Package     pgtype.Text
+	Switch      pgtype.Text
+	Const       pgtype.Text
+	Fallthrough pgtype.Text
+	If          pgtype.Text
+	Range       pgtype.Text
+	Type        pgtype.Text
+	Continue    pgtype.Text
+	For         pgtype.Text
+	Import      pgtype.Text
+	Return      pgtype.Text
+	Var         pgtype.Text
+}
diff --git a/internal/endtoend/testdata/params_go_keywords/postgresql/go/query.sql.go b/internal/endtoend/testdata/params_go_keywords/postgresql/go/query.sql.go
new file mode 100644
index 000000000..430bc74a6
--- /dev/null
+++ b/internal/endtoend/testdata/params_go_keywords/postgresql/go/query.sql.go
@@ -0,0 +1,512 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+//   sqlc v1.22.0
+// source: query.sql
+
+package querytest
+
+import (
+	"context"
+
+	"github.com/jackc/pgx/v5/pgtype"
+)
+
+const keywordBreak = `-- name: KeywordBreak :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordBreak(ctx context.Context, break_ string) error {
+	_, err := q.db.Exec(ctx, keywordBreak, break_)
+	return err
+}
+
+const keywordCase = `-- name: KeywordCase :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordCase(ctx context.Context, case_ string) error {
+	_, err := q.db.Exec(ctx, keywordCase, case_)
+	return err
+}
+
+const keywordChan = `-- name: KeywordChan :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordChan(ctx context.Context, chan_ string) error {
+	_, err := q.db.Exec(ctx, keywordChan, chan_)
+	return err
+}
+
+const keywordConst = `-- name: KeywordConst :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordConst(ctx context.Context, const_ string) error {
+	_, err := q.db.Exec(ctx, keywordConst, const_)
+	return err
+}
+
+const keywordContinue = `-- name: KeywordContinue :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordContinue(ctx context.Context, continue_ string) error {
+	_, err := q.db.Exec(ctx, keywordContinue, continue_)
+	return err
+}
+
+const keywordDefault = `-- name: KeywordDefault :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordDefault(ctx context.Context, default_ string) error {
+	_, err := q.db.Exec(ctx, keywordDefault, default_)
+	return err
+}
+
+const keywordDefer = `-- name: KeywordDefer :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordDefer(ctx context.Context, defer_ string) error {
+	_, err := q.db.Exec(ctx, keywordDefer, defer_)
+	return err
+}
+
+const keywordElse = `-- name: KeywordElse :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordElse(ctx context.Context, else_ string) error {
+	_, err := q.db.Exec(ctx, keywordElse, else_)
+	return err
+}
+
+const keywordFallthrough = `-- name: KeywordFallthrough :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordFallthrough(ctx context.Context, fallthrough_ string) error {
+	_, err := q.db.Exec(ctx, keywordFallthrough, fallthrough_)
+	return err
+}
+
+const keywordFor = `-- name: KeywordFor :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordFor(ctx context.Context, for_ string) error {
+	_, err := q.db.Exec(ctx, keywordFor, for_)
+	return err
+}
+
+const keywordFunc = `-- name: KeywordFunc :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordFunc(ctx context.Context, func_ string) error {
+	_, err := q.db.Exec(ctx, keywordFunc, func_)
+	return err
+}
+
+const keywordGo = `-- name: KeywordGo :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordGo(ctx context.Context, go_ string) error {
+	_, err := q.db.Exec(ctx, keywordGo, go_)
+	return err
+}
+
+const keywordGoto = `-- name: KeywordGoto :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordGoto(ctx context.Context, goto_ string) error {
+	_, err := q.db.Exec(ctx, keywordGoto, goto_)
+	return err
+}
+
+const keywordIf = `-- name: KeywordIf :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordIf(ctx context.Context, if_ string) error {
+	_, err := q.db.Exec(ctx, keywordIf, if_)
+	return err
+}
+
+const keywordImport = `-- name: KeywordImport :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordImport(ctx context.Context, import_ string) error {
+	_, err := q.db.Exec(ctx, keywordImport, import_)
+	return err
+}
+
+const keywordInterface = `-- name: KeywordInterface :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordInterface(ctx context.Context, interface_ string) error {
+	_, err := q.db.Exec(ctx, keywordInterface, interface_)
+	return err
+}
+
+const keywordMap = `-- name: KeywordMap :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordMap(ctx context.Context, map_ string) error {
+	_, err := q.db.Exec(ctx, keywordMap, map_)
+	return err
+}
+
+const keywordPackage = `-- name: KeywordPackage :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordPackage(ctx context.Context, package_ string) error {
+	_, err := q.db.Exec(ctx, keywordPackage, package_)
+	return err
+}
+
+const keywordRange = `-- name: KeywordRange :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordRange(ctx context.Context, range_ string) error {
+	_, err := q.db.Exec(ctx, keywordRange, range_)
+	return err
+}
+
+const keywordReturn = `-- name: KeywordReturn :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordReturn(ctx context.Context, return_ string) error {
+	_, err := q.db.Exec(ctx, keywordReturn, return_)
+	return err
+}
+
+const keywordSelect = `-- name: KeywordSelect :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordSelect(ctx context.Context, select_ string) error {
+	_, err := q.db.Exec(ctx, keywordSelect, select_)
+	return err
+}
+
+const keywordStruct = `-- name: KeywordStruct :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordStruct(ctx context.Context, struct_ string) error {
+	_, err := q.db.Exec(ctx, keywordStruct, struct_)
+	return err
+}
+
+const keywordSwitch = `-- name: KeywordSwitch :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordSwitch(ctx context.Context, switch_ string) error {
+	_, err := q.db.Exec(ctx, keywordSwitch, switch_)
+	return err
+}
+
+const keywordType = `-- name: KeywordType :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordType(ctx context.Context, type_ string) error {
+	_, err := q.db.Exec(ctx, keywordType, type_)
+	return err
+}
+
+const keywordVar = `-- name: KeywordVar :exec
+SELECT $1::text
+`
+
+func (q *Queries) KeywordVar(ctx context.Context, var_ string) error {
+	_, err := q.db.Exec(ctx, keywordVar, var_)
+	return err
+}
+
+const selectBreak = `-- name: SelectBreak :one
+SELECT "break" FROM go_keywords
+`
+
+func (q *Queries) SelectBreak(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectBreak)
+	var break_ pgtype.Text
+	err := row.Scan(&break_)
+	return break_, err
+}
+
+const selectCase = `-- name: SelectCase :one
+SELECT "case" FROM go_keywords
+`
+
+func (q *Queries) SelectCase(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectCase)
+	var case_ pgtype.Text
+	err := row.Scan(&case_)
+	return case_, err
+}
+
+const selectChan = `-- name: SelectChan :one
+SELECT "chan" FROM go_keywords
+`
+
+func (q *Queries) SelectChan(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectChan)
+	var chan_ pgtype.Text
+	err := row.Scan(&chan_)
+	return chan_, err
+}
+
+const selectConst = `-- name: SelectConst :one
+SELECT "const" FROM go_keywords
+`
+
+func (q *Queries) SelectConst(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectConst)
+	var const_ pgtype.Text
+	err := row.Scan(&const_)
+	return const_, err
+}
+
+const selectContinue = `-- name: SelectContinue :one
+SELECT "continue" FROM go_keywords
+`
+
+func (q *Queries) SelectContinue(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectContinue)
+	var continue_ pgtype.Text
+	err := row.Scan(&continue_)
+	return continue_, err
+}
+
+const selectDefault = `-- name: SelectDefault :one
+SELECT "default" FROM go_keywords
+`
+
+func (q *Queries) SelectDefault(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectDefault)
+	var default_ pgtype.Text
+	err := row.Scan(&default_)
+	return default_, err
+}
+
+const selectDefer = `-- name: SelectDefer :one
+SELECT "defer" FROM go_keywords
+`
+
+func (q *Queries) SelectDefer(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectDefer)
+	var defer_ pgtype.Text
+	err := row.Scan(&defer_)
+	return defer_, err
+}
+
+const selectElse = `-- name: SelectElse :one
+SELECT "else" FROM go_keywords
+`
+
+func (q *Queries) SelectElse(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectElse)
+	var else_ pgtype.Text
+	err := row.Scan(&else_)
+	return else_, err
+}
+
+const selectFallthrough = `-- name: SelectFallthrough :one
+SELECT "fallthrough" FROM go_keywords
+`
+
+func (q *Queries) SelectFallthrough(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectFallthrough)
+	var fallthrough_ pgtype.Text
+	err := row.Scan(&fallthrough_)
+	return fallthrough_, err
+}
+
+const selectFor = `-- name: SelectFor :one
+SELECT "for" FROM go_keywords
+`
+
+func (q *Queries) SelectFor(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectFor)
+	var for_ pgtype.Text
+	err := row.Scan(&for_)
+	return for_, err
+}
+
+const selectFunc = `-- name: SelectFunc :one
+SELECT "func" FROM go_keywords
+`
+
+func (q *Queries) SelectFunc(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectFunc)
+	var func_ pgtype.Text
+	err := row.Scan(&func_)
+	return func_, err
+}
+
+const selectGo = `-- name: SelectGo :one
+SELECT "go" FROM go_keywords
+`
+
+func (q *Queries) SelectGo(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectGo)
+	var go_ pgtype.Text
+	err := row.Scan(&go_)
+	return go_, err
+}
+
+const selectGoto = `-- name: SelectGoto :one
+SELECT "goto" FROM go_keywords
+`
+
+func (q *Queries) SelectGoto(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectGoto)
+	var goto_ pgtype.Text
+	err := row.Scan(&goto_)
+	return goto_, err
+}
+
+const selectIf = `-- name: SelectIf :one
+SELECT "if" FROM go_keywords
+`
+
+func (q *Queries) SelectIf(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectIf)
+	var if_ pgtype.Text
+	err := row.Scan(&if_)
+	return if_, err
+}
+
+const selectImport = `-- name: SelectImport :one
+SELECT "import" FROM go_keywords
+`
+
+func (q *Queries) SelectImport(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectImport)
+	var import_ pgtype.Text
+	err := row.Scan(&import_)
+	return import_, err
+}
+
+const selectInterface = `-- name: SelectInterface :one
+SELECT "interface" FROM go_keywords
+`
+
+func (q *Queries) SelectInterface(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectInterface)
+	var interface_ pgtype.Text
+	err := row.Scan(&interface_)
+	return interface_, err
+}
+
+const selectMap = `-- name: SelectMap :one
+SELECT "map" FROM go_keywords
+`
+
+func (q *Queries) SelectMap(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectMap)
+	var map_ pgtype.Text
+	err := row.Scan(&map_)
+	return map_, err
+}
+
+const selectPackage = `-- name: SelectPackage :one
+SELECT "package" FROM go_keywords
+`
+
+func (q *Queries) SelectPackage(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectPackage)
+	var package_ pgtype.Text
+	err := row.Scan(&package_)
+	return package_, err
+}
+
+const selectRange = `-- name: SelectRange :one
+SELECT "range" FROM go_keywords
+`
+
+func (q *Queries) SelectRange(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectRange)
+	var range_ pgtype.Text
+	err := row.Scan(&range_)
+	return range_, err
+}
+
+const selectReturn = `-- name: SelectReturn :one
+SELECT "return" FROM go_keywords
+`
+
+func (q *Queries) SelectReturn(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectReturn)
+	var return_ pgtype.Text
+	err := row.Scan(&return_)
+	return return_, err
+}
+
+const selectSelect = `-- name: SelectSelect :one
+SELECT "select" FROM go_keywords
+`
+
+func (q *Queries) SelectSelect(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectSelect)
+	var select_ pgtype.Text
+	err := row.Scan(&select_)
+	return select_, err
+}
+
+const selectStruct = `-- name: SelectStruct :one
+SELECT "struct" FROM go_keywords
+`
+
+func (q *Queries) SelectStruct(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectStruct)
+	var struct_ pgtype.Text
+	err := row.Scan(&struct_)
+	return struct_, err
+}
+
+const selectSwitch = `-- name: SelectSwitch :one
+SELECT "switch" FROM go_keywords
+`
+
+func (q *Queries) SelectSwitch(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectSwitch)
+	var switch_ pgtype.Text
+	err := row.Scan(&switch_)
+	return switch_, err
+}
+
+const selectType = `-- name: SelectType :one
+SELECT "type" FROM go_keywords
+`
+
+func (q *Queries) SelectType(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectType)
+	var type_ pgtype.Text
+	err := row.Scan(&type_)
+	return type_, err
+}
+
+const selectVar = `-- name: SelectVar :one
+SELECT "var" FROM go_keywords
+`
+
+func (q *Queries) SelectVar(ctx context.Context) (pgtype.Text, error) {
+	row := q.db.QueryRow(ctx, selectVar)
+	var var_ pgtype.Text
+	err := row.Scan(&var_)
+	return var_, err
+}
diff --git a/internal/endtoend/testdata/params_go_keywords/postgresql/query.sql b/internal/endtoend/testdata/params_go_keywords/postgresql/query.sql
new file mode 100644
index 000000000..70796e4e3
--- /dev/null
+++ b/internal/endtoend/testdata/params_go_keywords/postgresql/query.sql
@@ -0,0 +1,149 @@
+-- name: KeywordBreak :exec
+SELECT sqlc.arg('break')::text;
+
+-- name: KeywordDefault :exec
+SELECT sqlc.arg('default')::text;
+
+-- name: KeywordFunc :exec
+SELECT sqlc.arg('func')::text;
+
+-- name: KeywordInterface :exec
+SELECT sqlc.arg('interface')::text;
+
+-- name: KeywordSelect :exec
+SELECT sqlc.arg('select')::text;
+
+-- name: KeywordCase :exec
+SELECT sqlc.arg('case')::text;
+
+-- name: KeywordDefer :exec
+SELECT sqlc.arg('defer')::text;
+
+-- name: KeywordGo :exec
+SELECT sqlc.arg('go')::text;
+
+-- name: KeywordMap :exec
+SELECT sqlc.arg('map')::text;
+
+-- name: KeywordStruct :exec
+SELECT sqlc.arg('struct')::text;
+
+-- name: KeywordChan :exec
+SELECT sqlc.arg('chan')::text;
+
+-- name: KeywordElse :exec
+SELECT sqlc.arg('else')::text;
+
+-- name: KeywordGoto :exec
+SELECT sqlc.arg('goto')::text;
+
+-- name: KeywordPackage :exec
+SELECT sqlc.arg('package')::text;
+
+-- name: KeywordSwitch :exec
+SELECT sqlc.arg('switch')::text;
+
+-- name: KeywordConst :exec
+SELECT sqlc.arg('const')::text;
+
+-- name: KeywordFallthrough :exec
+SELECT sqlc.arg('fallthrough')::text;
+
+-- name: KeywordIf :exec
+SELECT sqlc.arg('if')::text;
+
+-- name: KeywordRange :exec
+SELECT sqlc.arg('range')::text;
+
+-- name: KeywordType :exec
+SELECT sqlc.arg('type')::text;
+
+-- name: KeywordContinue :exec
+SELECT sqlc.arg('continue')::text;
+
+-- name: KeywordFor :exec
+SELECT sqlc.arg('for')::text;
+
+-- name: KeywordImport :exec
+SELECT sqlc.arg('import')::text;
+
+-- name: KeywordReturn :exec
+SELECT sqlc.arg('return')::text;
+
+-- name: KeywordVar :exec
+SELECT sqlc.arg('var')::text;
+
+-- name: SelectBreak :one
+SELECT "break" FROM go_keywords;
+
+-- name: SelectDefault :one
+SELECT "default" FROM go_keywords;
+
+-- name: SelectFunc :one
+SELECT "func" FROM go_keywords;
+
+-- name: SelectInterface :one
+SELECT "interface" FROM go_keywords;
+
+-- name: SelectSelect :one
+SELECT "select" FROM go_keywords;
+
+-- name: SelectCase :one
+SELECT "case" FROM go_keywords;
+
+-- name: SelectDefer :one
+SELECT "defer" FROM go_keywords;
+
+-- name: SelectGo :one
+SELECT "go" FROM go_keywords;
+
+-- name: SelectMap :one
+SELECT "map" FROM go_keywords;
+
+-- name: SelectStruct :one
+SELECT "struct" FROM go_keywords;
+
+-- name: SelectChan :one
+SELECT "chan" FROM go_keywords;
+
+-- name: SelectElse :one
+SELECT "else" FROM go_keywords;
+
+-- name: SelectGoto :one
+SELECT "goto" FROM go_keywords;
+
+-- name: SelectPackage :one
+SELECT "package" FROM go_keywords;
+
+-- name: SelectSwitch :one
+SELECT "switch" FROM go_keywords;
+
+-- name: SelectConst :one
+SELECT "const" FROM go_keywords;
+
+-- name: SelectFallthrough :one
+SELECT "fallthrough" FROM go_keywords;
+
+-- name: SelectIf :one
+SELECT "if" FROM go_keywords;
+
+-- name: SelectRange :one
+SELECT "range" FROM go_keywords;
+
+-- name: SelectType :one
+SELECT "type" FROM go_keywords;
+
+-- name: SelectContinue :one
+SELECT "continue" FROM go_keywords;
+
+-- name: SelectFor :one
+SELECT "for" FROM go_keywords;
+
+-- name: SelectImport :one
+SELECT "import" FROM go_keywords;
+
+-- name: SelectReturn :one
+SELECT "return" FROM go_keywords;
+
+-- name: SelectVar :one
+SELECT "var" FROM go_keywords;
diff --git a/internal/endtoend/testdata/params_go_keywords/postgresql/schema.sql b/internal/endtoend/testdata/params_go_keywords/postgresql/schema.sql
new file mode 100644
index 000000000..11a04beef
--- /dev/null
+++ b/internal/endtoend/testdata/params_go_keywords/postgresql/schema.sql
@@ -0,0 +1,27 @@
+CREATE TABLE go_keywords (
+    "break" TEXT,
+    "default" TEXT,
+    "func" TEXT,
+    "interface" TEXT,
+    "select" TEXT,
+    "case" TEXT,
+    "defer" TEXT,
+    "go" TEXT,
+    "map" TEXT,
+    "struct" TEXT,
+    "chan" TEXT,
+    "else" TEXT,
+    "goto" TEXT,
+    "package" TEXT,
+    "switch" TEXT,
+    "const" TEXT,
+    "fallthrough" TEXT,
+    "if" TEXT,
+    "range" TEXT,
+    "type" TEXT,
+    "continue" TEXT,
+    "for" TEXT,
+    "import" TEXT,
+    "return" TEXT,
+    "var" TEXT
+);
diff --git a/internal/endtoend/testdata/params_go_keywords/postgresql/sqlc.yaml b/internal/endtoend/testdata/params_go_keywords/postgresql/sqlc.yaml
new file mode 100644
index 000000000..5dc63e3f9
--- /dev/null
+++ b/internal/endtoend/testdata/params_go_keywords/postgresql/sqlc.yaml
@@ -0,0 +1,10 @@
+version: "2"
+sql:
+  - engine: "postgresql"
+    schema: "schema.sql"
+    queries: "query.sql"
+    gen:
+      go:
+        package: "querytest"
+        out: "go"
+        sql_package: "pgx/v5"