From 0842233f86980a92db33077c8e95b2baa694bca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=A7=84=EC=84=9C?= Date: Fri, 6 Nov 2026 19:24:07 +0900 Subject: [PATCH] fix(compiler): reject sqlc.embed on the nullable side of an outer join An embedded struct is the model of its table, whose columns may not be nullable. When the table is on the nullable side of a LEFT, RIGHT or FULL JOIN the query returns NULLs the struct cannot hold, so the generated code compiled but failed at runtime with "converting NULL to string is unsupported". Report an error at generate time instead. The join walk is the one that already makes plain columns nullable (isTableRequired); embeds skipped it because their column carries no table. Only the top-level FROM clause is checked, as for plain columns. The error is returned together with the columns. A database analyzer discards inference errors and replaces the columns when none come back, which would have turned the embed into flat columns. Updates #2997 --- internal/compiler/output_columns.go | 35 ++++- .../postgresql/exec.json | 3 + .../postgresql/query.sql | 3 + .../postgresql/schema.sql | 9 ++ .../postgresql/sqlc.json | 12 ++ .../postgresql/stderr.txt | 2 + .../sqlite/exec.json | 3 + .../sqlite/query.sql | 3 + .../sqlite/schema.sql | 9 ++ .../sqlite/sqlc.json | 12 ++ .../sqlite/stderr.txt | 2 + .../sqlc_embed_outer_join_ok/sqlite/go/db.go | 31 ++++ .../sqlite/go/models.go | 19 +++ .../sqlite/go/query.sql.go | 148 ++++++++++++++++++ .../sqlc_embed_outer_join_ok/sqlite/query.sql | 15 ++ .../sqlite/schema.sql | 9 ++ .../sqlc_embed_outer_join_ok/sqlite/sqlc.json | 12 ++ .../sqlite/exec.json | 3 + .../sqlite/query.sql | 3 + .../sqlite/schema.sql | 9 ++ .../sqlite/sqlc.json | 12 ++ .../sqlite/stderr.txt | 2 + .../sqlite/exec.json | 3 + .../sqlite/query.sql | 3 + .../sqlite/schema.sql | 9 ++ .../sqlite/sqlc.json | 12 ++ .../sqlite/stderr.txt | 2 + internal/sql/preprocess/preprocess.go | 5 + 28 files changed, 389 insertions(+), 1 deletion(-) create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/exec.json create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/query.sql create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/schema.sql create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/sqlc.json create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/stderr.txt create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/exec.json create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/query.sql create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/schema.sql create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/sqlc.json create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/stderr.txt create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/go/db.go create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/go/models.go create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/go/query.sql.go create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/query.sql create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/schema.sql create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/sqlc.json create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/exec.json create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/query.sql create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/schema.sql create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/sqlc.json create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/stderr.txt create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/exec.json create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/query.sql create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/schema.sql create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/sqlc.json create mode 100644 internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/stderr.txt diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index 557fb7d575..04f532a403 100644 --- a/internal/compiler/output_columns.go +++ b/internal/compiler/output_columns.go @@ -9,6 +9,7 @@ import ( "github.com/sqlc-dev/sqlc/internal/sql/astutils" "github.com/sqlc-dev/sqlc/internal/sql/catalog" "github.com/sqlc-dev/sqlc/internal/sql/lang" + "github.com/sqlc-dev/sqlc/internal/sql/preprocess" "github.com/sqlc-dev/sqlc/internal/sql/sqlerr" ) @@ -53,6 +54,8 @@ func hasStarRef(cf *ast.ColumnRef) bool { // Return an error if column references are ambiguous // Return an error if column references don't exist func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, error) { + var embedErr error + tables, err := c.sourceTables(qc, node) if err != nil { return nil, err @@ -267,6 +270,11 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er // add a column with a reference to an embedded table if embed, ok := qc.embeds.Find(n.Location, res.Location); ok { + // Keep the column and report the error with the rest, so an + // analyzer that discards inference errors still sees the embed. + if err := checkEmbedRequired(node, embed, res.Location); err != nil && embedErr == nil { + embedErr = err + } cols = append(cols, &Column{ Name: embed.Table.Name, EmbedTable: embed.Table, @@ -416,7 +424,7 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er } } - return cols, nil + return cols, embedErr } const ( @@ -467,6 +475,31 @@ func isTableRequired(n ast.Node, col *Column, prior int) int { return tableNotFound } +// An embedded struct is the model of its table, whose columns may not be +// nullable, so it cannot hold the NULLs the optional side of an outer join +// produces. +func checkEmbedRequired(node ast.Node, embed *preprocess.Embed, location int) error { + n, ok := node.(*ast.SelectStmt) + if !ok { + return nil + } + col := &Column{Table: embed.Table, TableAlias: embed.Param()} + for _, f := range n.FromClause.Items { + res := isTableRequired(f, col, tableRequired) + if res != tableNotFound { + if res == tableOptional { + return &sqlerr.Error{ + Code: "0A000", + Message: fmt.Sprintf("sqlc.embed(%s) on the nullable side of an outer join is not supported", embed.Param()), + Location: location, + } + } + break + } + } + return nil +} + type tableVisitor struct { list ast.List } diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/exec.json b/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/exec.json new file mode 100644 index 0000000000..97f81fbc66 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/query.sql b/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/query.sql new file mode 100644 index 0000000000..b5bda1cd8b --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/query.sql @@ -0,0 +1,3 @@ +-- name: LeftJoin :many +SELECT sqlc.embed(posts), sqlc.embed(users) FROM posts +LEFT JOIN users ON posts.user_id = users.id; diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/schema.sql b/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/schema.sql new file mode 100644 index 0000000000..1b9ac96407 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE users ( + id integer PRIMARY KEY, + name text NOT NULL +); + +CREATE TABLE posts ( + id integer PRIMARY KEY, + user_id integer +); diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/sqlc.json b/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/sqlc.json new file mode 100644 index 0000000000..f717ca2e66 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "postgresql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/stderr.txt b/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/stderr.txt new file mode 100644 index 0000000000..4871db3acf --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_left/postgresql/stderr.txt @@ -0,0 +1,2 @@ +# package querytest +query.sql:2:27: sqlc.embed(users) on the nullable side of an outer join is not supported diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/exec.json b/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/exec.json new file mode 100644 index 0000000000..97f81fbc66 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/query.sql b/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/query.sql new file mode 100644 index 0000000000..d28c7d522b --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/query.sql @@ -0,0 +1,3 @@ +-- name: LeftJoin :many +SELECT sqlc.embed(p), sqlc.embed(u) FROM posts AS p +LEFT JOIN users AS u ON p.user_id = u.id; diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/schema.sql b/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/schema.sql new file mode 100644 index 0000000000..1b9ac96407 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE users ( + id integer PRIMARY KEY, + name text NOT NULL +); + +CREATE TABLE posts ( + id integer PRIMARY KEY, + user_id integer +); diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/sqlc.json b/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/sqlc.json new file mode 100644 index 0000000000..cd66df063b --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "sqlite", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/stderr.txt b/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/stderr.txt new file mode 100644 index 0000000000..c9c0993cb0 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_left/sqlite/stderr.txt @@ -0,0 +1,2 @@ +# package querytest +query.sql:2:23: sqlc.embed(u) on the nullable side of an outer join is not supported diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/go/db.go b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/go/db.go new file mode 100644 index 0000000000..32faa9f2b8 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "context" + "database/sql" +) + +type DBTX interface { + ExecContext(context.Context, string, ...any) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...any) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...any) *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/sqlc_embed_outer_join_ok/sqlite/go/models.go b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/go/models.go new file mode 100644 index 0000000000..66d8bf17ad --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/go/models.go @@ -0,0 +1,19 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 + +package querytest + +import ( + "database/sql" +) + +type Post struct { + ID int64 + UserID sql.NullInt64 +} + +type User struct { + ID int64 + Name string +} diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/go/query.sql.go b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/go/query.sql.go new file mode 100644 index 0000000000..19f0069b0d --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/go/query.sql.go @@ -0,0 +1,148 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.31.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const innerJoin = `-- name: InnerJoin :many +SELECT p.id, p.user_id, u.id, u.name FROM posts AS p +INNER JOIN users AS u ON p.user_id = u.id +` + +type InnerJoinRow struct { + Post Post + User User +} + +func (q *Queries) InnerJoin(ctx context.Context) ([]InnerJoinRow, error) { + rows, err := q.db.QueryContext(ctx, innerJoin) + if err != nil { + return nil, err + } + defer rows.Close() + var items []InnerJoinRow + for rows.Next() { + var i InnerJoinRow + if err := rows.Scan( + &i.Post.ID, + &i.Post.UserID, + &i.User.ID, + &i.User.Name, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const leftSideOfLeftJoin = `-- name: LeftSideOfLeftJoin :many +SELECT p.id, p.user_id, u.name FROM posts AS p +LEFT JOIN users AS u ON p.user_id = u.id +` + +type LeftSideOfLeftJoinRow struct { + Post Post + Name sql.NullString +} + +func (q *Queries) LeftSideOfLeftJoin(ctx context.Context) ([]LeftSideOfLeftJoinRow, error) { + rows, err := q.db.QueryContext(ctx, leftSideOfLeftJoin) + if err != nil { + return nil, err + } + defer rows.Close() + var items []LeftSideOfLeftJoinRow + for rows.Next() { + var i LeftSideOfLeftJoinRow + if err := rows.Scan(&i.Post.ID, &i.Post.UserID, &i.Name); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const rightSideOfRightJoin = `-- name: RightSideOfRightJoin :many +SELECT u.id, u.name, p.id FROM posts AS p +RIGHT JOIN users AS u ON p.user_id = u.id +` + +type RightSideOfRightJoinRow struct { + User User + ID sql.NullInt64 +} + +func (q *Queries) RightSideOfRightJoin(ctx context.Context) ([]RightSideOfRightJoinRow, error) { + rows, err := q.db.QueryContext(ctx, rightSideOfRightJoin) + if err != nil { + return nil, err + } + defer rows.Close() + var items []RightSideOfRightJoinRow + for rows.Next() { + var i RightSideOfRightJoinRow + if err := rows.Scan(&i.User.ID, &i.User.Name, &i.ID); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const selfJoinRequiredAlias = `-- name: SelfJoinRequiredAlias :many +SELECT a.id, a.name, b.name FROM users AS a +LEFT JOIN users AS b ON a.id = b.id +` + +type SelfJoinRequiredAliasRow struct { + User User + Name sql.NullString +} + +func (q *Queries) SelfJoinRequiredAlias(ctx context.Context) ([]SelfJoinRequiredAliasRow, error) { + rows, err := q.db.QueryContext(ctx, selfJoinRequiredAlias) + if err != nil { + return nil, err + } + defer rows.Close() + var items []SelfJoinRequiredAliasRow + for rows.Next() { + var i SelfJoinRequiredAliasRow + if err := rows.Scan(&i.User.ID, &i.User.Name, &i.Name); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/query.sql b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/query.sql new file mode 100644 index 0000000000..53cfa81854 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/query.sql @@ -0,0 +1,15 @@ +-- name: LeftSideOfLeftJoin :many +SELECT sqlc.embed(p), u.name FROM posts AS p +LEFT JOIN users AS u ON p.user_id = u.id; + +-- name: RightSideOfRightJoin :many +SELECT sqlc.embed(u), p.id FROM posts AS p +RIGHT JOIN users AS u ON p.user_id = u.id; + +-- name: InnerJoin :many +SELECT sqlc.embed(p), sqlc.embed(u) FROM posts AS p +INNER JOIN users AS u ON p.user_id = u.id; + +-- name: SelfJoinRequiredAlias :many +SELECT sqlc.embed(a), b.name FROM users AS a +LEFT JOIN users AS b ON a.id = b.id; diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/schema.sql b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/schema.sql new file mode 100644 index 0000000000..1b9ac96407 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE users ( + id integer PRIMARY KEY, + name text NOT NULL +); + +CREATE TABLE posts ( + id integer PRIMARY KEY, + user_id integer +); diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/sqlc.json b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/sqlc.json new file mode 100644 index 0000000000..cd66df063b --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_ok/sqlite/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "sqlite", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/exec.json b/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/exec.json new file mode 100644 index 0000000000..97f81fbc66 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/query.sql b/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/query.sql new file mode 100644 index 0000000000..47252eb2fc --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/query.sql @@ -0,0 +1,3 @@ +-- name: RightJoin :many +SELECT sqlc.embed(p), sqlc.embed(u) FROM posts AS p +RIGHT JOIN users AS u ON p.user_id = u.id; diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/schema.sql b/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/schema.sql new file mode 100644 index 0000000000..1b9ac96407 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE users ( + id integer PRIMARY KEY, + name text NOT NULL +); + +CREATE TABLE posts ( + id integer PRIMARY KEY, + user_id integer +); diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/sqlc.json b/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/sqlc.json new file mode 100644 index 0000000000..cd66df063b --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "sqlite", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/stderr.txt b/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/stderr.txt new file mode 100644 index 0000000000..787617a074 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_right/sqlite/stderr.txt @@ -0,0 +1,2 @@ +# package querytest +query.sql:2:8: sqlc.embed(p) on the nullable side of an outer join is not supported diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/exec.json b/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/exec.json new file mode 100644 index 0000000000..97f81fbc66 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/exec.json @@ -0,0 +1,3 @@ +{ + "contexts": ["base"] +} diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/query.sql b/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/query.sql new file mode 100644 index 0000000000..559ebea2d1 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/query.sql @@ -0,0 +1,3 @@ +-- name: SelfJoin :many +SELECT sqlc.embed(a), sqlc.embed(b) FROM users AS a +LEFT JOIN users AS b ON a.id = b.id; diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/schema.sql b/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/schema.sql new file mode 100644 index 0000000000..1b9ac96407 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE users ( + id integer PRIMARY KEY, + name text NOT NULL +); + +CREATE TABLE posts ( + id integer PRIMARY KEY, + user_id integer +); diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/sqlc.json b/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/sqlc.json new file mode 100644 index 0000000000..cd66df063b --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "sqlite", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/stderr.txt b/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/stderr.txt new file mode 100644 index 0000000000..a0b94b1ca2 --- /dev/null +++ b/internal/endtoend/testdata/sqlc_embed_outer_join_self/sqlite/stderr.txt @@ -0,0 +1,2 @@ +# package querytest +query.sql:2:23: sqlc.embed(b) on the nullable side of an outer join is not supported diff --git a/internal/sql/preprocess/preprocess.go b/internal/sql/preprocess/preprocess.go index ad25bc9b2f..eea45a60e9 100644 --- a/internal/sql/preprocess/preprocess.go +++ b/internal/sql/preprocess/preprocess.go @@ -39,6 +39,11 @@ func (e Embed) Orig() string { return fmt.Sprintf("sqlc.embed(%s)", e.param) } +// Param returns the table name or alias the embed was written with. +func (e Embed) Param() string { + return e.param +} + // EmbedSet is the set of embeds in a single statement. type EmbedSet []*Embed