From 093ea55a48d5c866231a8922aca228faf16544e5 Mon Sep 17 00:00:00 2001 From: ryu-ichiroh Date: Thu, 3 Aug 2023 09:10:09 +0900 Subject: [PATCH] fixing the code to make the subselect nullable --- internal/compiler/output_columns.go | 26 ++--- internal/compiler/query.go | 1 + .../nullable_subselect/mysql/go/models.go | 5 + .../nullable_subselect/mysql/go/query.sql.go | 71 +++++++++---- .../nullable_subselect/mysql/query.sql | 13 ++- .../nullable_subselect/mysql/schema.sql | 7 ++ .../nullable_subselect/mysql/sqlc.json | 2 +- .../postgresql/pgx/v4/go/models.go | 5 + .../postgresql/pgx/v4/go/query.sql.go | 68 ++++++++---- .../postgresql/pgx/v4/query.sql | 13 ++- .../postgresql/pgx/v4/schema.sql | 7 ++ .../postgresql/pgx/v4/sqlc.json | 2 +- .../postgresql/pgx/v5/go/models.go | 5 + .../postgresql/pgx/v5/go/query.sql.go | 69 ++++++++---- .../postgresql/pgx/v5/query.sql | 13 ++- .../postgresql/pgx/v5/schema.sql | 7 ++ .../postgresql/pgx/v5/sqlc.json | 2 +- .../postgresql/stdlib/go/models.go | 5 + .../postgresql/stdlib/go/query.sql.go | 71 +++++++++---- .../postgresql/stdlib/query.sql | 13 ++- .../postgresql/stdlib/schema.sql | 7 ++ .../postgresql/stdlib/sqlc.json | 2 +- .../nullable_subselect/sqlite/go/models.go | 5 + .../nullable_subselect/sqlite/go/query.sql.go | 100 +++++++++++++++--- .../nullable_subselect/sqlite/query.sql | 17 ++- .../nullable_subselect/sqlite/schema.sql | 7 ++ .../nullable_subselect/sqlite/sqlc.json | 4 +- .../select_exists/sqlite/go/query.sql.go | 5 +- .../select_nested_count/mysql/go/query.sql.go | 2 +- .../sqlite/go/query.sql.go | 2 +- 30 files changed, 417 insertions(+), 139 deletions(-) create mode 100644 internal/endtoend/testdata/nullable_subselect/mysql/schema.sql create mode 100644 internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/schema.sql create mode 100644 internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/schema.sql create mode 100644 internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/schema.sql create mode 100644 internal/endtoend/testdata/nullable_subselect/sqlite/schema.sql diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index 2b3edaaf28..4b1510a52d 100644 --- a/internal/compiler/output_columns.go +++ b/internal/compiler/output_columns.go @@ -3,6 +3,7 @@ package compiler import ( "errors" "fmt" + "strings" "github.com/sqlc-dev/sqlc/internal/sql/catalog" @@ -315,12 +316,14 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er DataType: dataType(fun.ReturnType), NotNull: !fun.ReturnTypeNullable, IsFuncCall: true, + FuncName: rel.Name, }) } else { cols = append(cols, &Column{ Name: name, DataType: "any", IsFuncCall: true, + FuncName: rel.Name, }) } @@ -341,6 +344,10 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er if res.Name != nil { first.Name = *res.Name } + if !(first.IsFuncCall && strings.EqualFold(first.FuncName, "count")) { + first.NotNull = false + } + cols = append(cols, first) default: cols = append(cols, &Column{Name: name, DataType: "any", NotNull: false}) @@ -377,8 +384,9 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er if res.Name != nil { first.Name = *res.Name } - - if hasWhereOrHavingClause(n) { + if !(first.IsFuncCall && + (strings.EqualFold(first.FuncName, "count") || + strings.EqualFold(first.FuncName, "total"))) { first.NotNull = false } @@ -718,17 +726,3 @@ func findColumnForRef(ref *ast.ColumnRef, tables []*Table, targetList *ast.List) return nil } - -// hasWhereOrHavingClause returns true if the statement contains WHERE or HAVING clause -func hasWhereOrHavingClause(node ast.Node) bool { - stmt := node.(*ast.SelectStmt) - - if _, isTODO := stmt.WhereClause.(*ast.TODO); stmt.WhereClause != nil && !isTODO { - return true - } - if _, isTODO := stmt.HavingClause.(*ast.TODO); stmt.HavingClause != nil && !isTODO { - return true - } - - return false -} diff --git a/internal/compiler/query.go b/internal/compiler/query.go index 3505c19071..681911852c 100644 --- a/internal/compiler/query.go +++ b/internal/compiler/query.go @@ -25,6 +25,7 @@ type Column struct { Length *int IsNamedParam bool IsFuncCall bool + FuncName string // XXX: Figure out what PostgreSQL calls `foo.id` Scope string diff --git a/internal/endtoend/testdata/nullable_subselect/mysql/go/models.go b/internal/endtoend/testdata/nullable_subselect/mysql/go/models.go index b29372db24..56915dca09 100644 --- a/internal/endtoend/testdata/nullable_subselect/mysql/go/models.go +++ b/internal/endtoend/testdata/nullable_subselect/mysql/go/models.go @@ -8,6 +8,11 @@ import ( "database/sql" ) +type Empty struct { + A int32 + B sql.NullInt32 +} + type Foo struct { A int32 B sql.NullInt32 diff --git a/internal/endtoend/testdata/nullable_subselect/mysql/go/query.sql.go b/internal/endtoend/testdata/nullable_subselect/mysql/go/query.sql.go index 19d9c71d93..d6d4ec0158 100644 --- a/internal/endtoend/testdata/nullable_subselect/mysql/go/query.sql.go +++ b/internal/endtoend/testdata/nullable_subselect/mysql/go/query.sql.go @@ -7,27 +7,30 @@ package querytest import ( "context" + "database/sql" ) -const subqueryWithHavingClause = `-- name: SubqueryWithHavingClause :many -SELECT a, (SELECT COUNT(a) FROM foo GROUP BY b HAVING COUNT(a) > 10) as "total" FROM foo +const countRowsEmptyTable = `-- name: CountRowsEmptyTable :many +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo ` -type SubqueryWithHavingClauseRow struct { +type CountRowsEmptyTableRow struct { A int32 - Total int64 + Count int64 } -func (q *Queries) SubqueryWithHavingClause(ctx context.Context) ([]SubqueryWithHavingClauseRow, error) { - rows, err := q.db.QueryContext(ctx, subqueryWithHavingClause) +// In MySQL, only count() returns 0 for empty table. +// https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html +func (q *Queries) CountRowsEmptyTable(ctx context.Context) ([]CountRowsEmptyTableRow, error) { + rows, err := q.db.QueryContext(ctx, countRowsEmptyTable) if err != nil { return nil, err } defer rows.Close() - var items []SubqueryWithHavingClauseRow + var items []CountRowsEmptyTableRow for rows.Next() { - var i SubqueryWithHavingClauseRow - if err := rows.Scan(&i.A, &i.Total); err != nil { + var i CountRowsEmptyTableRow + if err := rows.Scan(&i.A, &i.Count); err != nil { return nil, err } items = append(items, i) @@ -41,25 +44,57 @@ func (q *Queries) SubqueryWithHavingClause(ctx context.Context) ([]SubqueryWithH return items, nil } -const subqueryWithWhereClause = `-- name: SubqueryWithWhereClause :many -SELECT a, (SELECT COUNT(a) FROM foo WHERE a > 10) as "total" FROM foo +const firstRowFromEmptyTable = `-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" FROM foo ` -type SubqueryWithWhereClauseRow struct { +type FirstRowFromEmptyTableRow struct { A int32 - Total int64 + First sql.NullInt32 } -func (q *Queries) SubqueryWithWhereClause(ctx context.Context) ([]SubqueryWithWhereClauseRow, error) { - rows, err := q.db.QueryContext(ctx, subqueryWithWhereClause) +func (q *Queries) FirstRowFromEmptyTable(ctx context.Context) ([]FirstRowFromEmptyTableRow, error) { + rows, err := q.db.QueryContext(ctx, firstRowFromEmptyTable) if err != nil { return nil, err } defer rows.Close() - var items []SubqueryWithWhereClauseRow + var items []FirstRowFromEmptyTableRow for rows.Next() { - var i SubqueryWithWhereClauseRow - if err := rows.Scan(&i.A, &i.Total); err != nil { + var i FirstRowFromEmptyTableRow + if err := rows.Scan(&i.A, &i.First); 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 firstRowFromFooTable = `-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo +` + +type FirstRowFromFooTableRow struct { + A int32 + First sql.NullInt32 +} + +func (q *Queries) FirstRowFromFooTable(ctx context.Context) ([]FirstRowFromFooTableRow, error) { + rows, err := q.db.QueryContext(ctx, firstRowFromFooTable) + if err != nil { + return nil, err + } + defer rows.Close() + var items []FirstRowFromFooTableRow + for rows.Next() { + var i FirstRowFromFooTableRow + if err := rows.Scan(&i.A, &i.First); err != nil { return nil, err } items = append(items, i) diff --git a/internal/endtoend/testdata/nullable_subselect/mysql/query.sql b/internal/endtoend/testdata/nullable_subselect/mysql/query.sql index d68a4cb80d..9489d1abbb 100644 --- a/internal/endtoend/testdata/nullable_subselect/mysql/query.sql +++ b/internal/endtoend/testdata/nullable_subselect/mysql/query.sql @@ -1,7 +1,10 @@ -CREATE TABLE foo (a int not null, b int); +-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo; --- name: SubqueryWithWhereClause :many -SELECT a, (SELECT COUNT(a) FROM foo WHERE a > 10) as "total" FROM foo; +-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" FROM foo; --- name: SubqueryWithHavingClause :many -SELECT a, (SELECT COUNT(a) FROM foo GROUP BY b HAVING COUNT(a) > 10) as "total" FROM foo; +-- In MySQL, only count() returns 0 for empty table. +-- https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html +-- name: CountRowsEmptyTable :many +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo; diff --git a/internal/endtoend/testdata/nullable_subselect/mysql/schema.sql b/internal/endtoend/testdata/nullable_subselect/mysql/schema.sql new file mode 100644 index 0000000000..1119555fdf --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/mysql/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE foo (a int not NULL, b int); + +INSERT INTO foo VALUES (1, 2); +INSERT INTO foo VALUES (3, NULL); +INSERT INTO foo VALUES (4, 5); + +CREATE TABLE empty (a int not NULL, b int); diff --git a/internal/endtoend/testdata/nullable_subselect/mysql/sqlc.json b/internal/endtoend/testdata/nullable_subselect/mysql/sqlc.json index 445bbd1589..e41c39e8b3 100644 --- a/internal/endtoend/testdata/nullable_subselect/mysql/sqlc.json +++ b/internal/endtoend/testdata/nullable_subselect/mysql/sqlc.json @@ -5,7 +5,7 @@ "path": "go", "engine": "mysql", "name": "querytest", - "schema": "query.sql", + "schema": "schema.sql", "queries": "query.sql" } ] diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/models.go b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/models.go index b29372db24..56915dca09 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/models.go +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/models.go @@ -8,6 +8,11 @@ import ( "database/sql" ) +type Empty struct { + A int32 + B sql.NullInt32 +} + type Foo struct { A int32 B sql.NullInt32 diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/query.sql.go b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/query.sql.go index c1ef865dad..7578328ecc 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/query.sql.go +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/query.sql.go @@ -7,27 +7,30 @@ package querytest import ( "context" + "database/sql" ) -const subqueryWithHavingClause = `-- name: SubqueryWithHavingClause :many -SELECT a, (SELECT COUNT(a) FROM foo GROUP BY b HAVING COUNT(a) > 10) as "total" FROM foo +const countRowsEmptyTable = `-- name: CountRowsEmptyTable :many +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo ` -type SubqueryWithHavingClauseRow struct { +type CountRowsEmptyTableRow struct { A int32 - Total int64 + Count int64 } -func (q *Queries) SubqueryWithHavingClause(ctx context.Context) ([]SubqueryWithHavingClauseRow, error) { - rows, err := q.db.Query(ctx, subqueryWithHavingClause) +// In PostgreSQL, only count() returns 0 for empty table. +// https://www.postgresql.org/docs/15/functions-aggregate.html +func (q *Queries) CountRowsEmptyTable(ctx context.Context) ([]CountRowsEmptyTableRow, error) { + rows, err := q.db.Query(ctx, countRowsEmptyTable) if err != nil { return nil, err } defer rows.Close() - var items []SubqueryWithHavingClauseRow + var items []CountRowsEmptyTableRow for rows.Next() { - var i SubqueryWithHavingClauseRow - if err := rows.Scan(&i.A, &i.Total); err != nil { + var i CountRowsEmptyTableRow + if err := rows.Scan(&i.A, &i.Count); err != nil { return nil, err } items = append(items, i) @@ -38,25 +41,54 @@ func (q *Queries) SubqueryWithHavingClause(ctx context.Context) ([]SubqueryWithH return items, nil } -const subqueryWithWhereClause = `-- name: SubqueryWithWhereClause :many -SELECT a, (SELECT COUNT(a) FROM foo WHERE a > 10) as "total" FROM foo +const firstRowFromEmptyTable = `-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" FROM foo ` -type SubqueryWithWhereClauseRow struct { +type FirstRowFromEmptyTableRow struct { A int32 - Total int64 + First sql.NullInt32 } -func (q *Queries) SubqueryWithWhereClause(ctx context.Context) ([]SubqueryWithWhereClauseRow, error) { - rows, err := q.db.Query(ctx, subqueryWithWhereClause) +func (q *Queries) FirstRowFromEmptyTable(ctx context.Context) ([]FirstRowFromEmptyTableRow, error) { + rows, err := q.db.Query(ctx, firstRowFromEmptyTable) if err != nil { return nil, err } defer rows.Close() - var items []SubqueryWithWhereClauseRow + var items []FirstRowFromEmptyTableRow for rows.Next() { - var i SubqueryWithWhereClauseRow - if err := rows.Scan(&i.A, &i.Total); err != nil { + var i FirstRowFromEmptyTableRow + if err := rows.Scan(&i.A, &i.First); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const firstRowFromFooTable = `-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo +` + +type FirstRowFromFooTableRow struct { + A int32 + First sql.NullInt32 +} + +func (q *Queries) FirstRowFromFooTable(ctx context.Context) ([]FirstRowFromFooTableRow, error) { + rows, err := q.db.Query(ctx, firstRowFromFooTable) + if err != nil { + return nil, err + } + defer rows.Close() + var items []FirstRowFromFooTableRow + for rows.Next() { + var i FirstRowFromFooTableRow + if err := rows.Scan(&i.A, &i.First); err != nil { return nil, err } items = append(items, i) diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/query.sql b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/query.sql index d68a4cb80d..ace2ad2d77 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/query.sql +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/query.sql @@ -1,7 +1,10 @@ -CREATE TABLE foo (a int not null, b int); +-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo; --- name: SubqueryWithWhereClause :many -SELECT a, (SELECT COUNT(a) FROM foo WHERE a > 10) as "total" FROM foo; +-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" FROM foo; --- name: SubqueryWithHavingClause :many -SELECT a, (SELECT COUNT(a) FROM foo GROUP BY b HAVING COUNT(a) > 10) as "total" FROM foo; +-- In PostgreSQL, only count() returns 0 for empty table. +-- https://www.postgresql.org/docs/15/functions-aggregate.html +-- name: CountRowsEmptyTable :many +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo; diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/schema.sql b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/schema.sql new file mode 100644 index 0000000000..1119555fdf --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE foo (a int not NULL, b int); + +INSERT INTO foo VALUES (1, 2); +INSERT INTO foo VALUES (3, NULL); +INSERT INTO foo VALUES (4, 5); + +CREATE TABLE empty (a int not NULL, b int); diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/sqlc.json b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/sqlc.json index 9403bd0279..d1244c9e7a 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/sqlc.json +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/sqlc.json @@ -6,7 +6,7 @@ "engine": "postgresql", "sql_package": "pgx/v4", "name": "querytest", - "schema": "query.sql", + "schema": "schema.sql", "queries": "query.sql" } ] diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/models.go b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/models.go index fd7cf619ba..37e9f1ac45 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/models.go +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/models.go @@ -8,6 +8,11 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +type Empty struct { + A int32 + B pgtype.Int4 +} + type Foo struct { A int32 B pgtype.Int4 diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/query.sql.go b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/query.sql.go index c1ef865dad..513abf58da 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/query.sql.go +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/query.sql.go @@ -7,27 +7,60 @@ package querytest import ( "context" + + "github.com/jackc/pgx/v5/pgtype" ) -const subqueryWithHavingClause = `-- name: SubqueryWithHavingClause :many -SELECT a, (SELECT COUNT(a) FROM foo GROUP BY b HAVING COUNT(a) > 10) as "total" FROM foo +const countRowsEmptyTable = `-- name: CountRowsEmptyTable :many +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo +` + +type CountRowsEmptyTableRow struct { + A int32 + Count int64 +} + +// In PostgreSQL, only count() returns 0 for empty table. +// https://www.postgresql.org/docs/15/functions-aggregate.html +func (q *Queries) CountRowsEmptyTable(ctx context.Context) ([]CountRowsEmptyTableRow, error) { + rows, err := q.db.Query(ctx, countRowsEmptyTable) + if err != nil { + return nil, err + } + defer rows.Close() + var items []CountRowsEmptyTableRow + for rows.Next() { + var i CountRowsEmptyTableRow + if err := rows.Scan(&i.A, &i.Count); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const firstRowFromEmptyTable = `-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" FROM foo ` -type SubqueryWithHavingClauseRow struct { +type FirstRowFromEmptyTableRow struct { A int32 - Total int64 + First pgtype.Int4 } -func (q *Queries) SubqueryWithHavingClause(ctx context.Context) ([]SubqueryWithHavingClauseRow, error) { - rows, err := q.db.Query(ctx, subqueryWithHavingClause) +func (q *Queries) FirstRowFromEmptyTable(ctx context.Context) ([]FirstRowFromEmptyTableRow, error) { + rows, err := q.db.Query(ctx, firstRowFromEmptyTable) if err != nil { return nil, err } defer rows.Close() - var items []SubqueryWithHavingClauseRow + var items []FirstRowFromEmptyTableRow for rows.Next() { - var i SubqueryWithHavingClauseRow - if err := rows.Scan(&i.A, &i.Total); err != nil { + var i FirstRowFromEmptyTableRow + if err := rows.Scan(&i.A, &i.First); err != nil { return nil, err } items = append(items, i) @@ -38,25 +71,25 @@ func (q *Queries) SubqueryWithHavingClause(ctx context.Context) ([]SubqueryWithH return items, nil } -const subqueryWithWhereClause = `-- name: SubqueryWithWhereClause :many -SELECT a, (SELECT COUNT(a) FROM foo WHERE a > 10) as "total" FROM foo +const firstRowFromFooTable = `-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo ` -type SubqueryWithWhereClauseRow struct { +type FirstRowFromFooTableRow struct { A int32 - Total int64 + First pgtype.Int4 } -func (q *Queries) SubqueryWithWhereClause(ctx context.Context) ([]SubqueryWithWhereClauseRow, error) { - rows, err := q.db.Query(ctx, subqueryWithWhereClause) +func (q *Queries) FirstRowFromFooTable(ctx context.Context) ([]FirstRowFromFooTableRow, error) { + rows, err := q.db.Query(ctx, firstRowFromFooTable) if err != nil { return nil, err } defer rows.Close() - var items []SubqueryWithWhereClauseRow + var items []FirstRowFromFooTableRow for rows.Next() { - var i SubqueryWithWhereClauseRow - if err := rows.Scan(&i.A, &i.Total); err != nil { + var i FirstRowFromFooTableRow + if err := rows.Scan(&i.A, &i.First); err != nil { return nil, err } items = append(items, i) diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/query.sql b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/query.sql index d68a4cb80d..ace2ad2d77 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/query.sql +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/query.sql @@ -1,7 +1,10 @@ -CREATE TABLE foo (a int not null, b int); +-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo; --- name: SubqueryWithWhereClause :many -SELECT a, (SELECT COUNT(a) FROM foo WHERE a > 10) as "total" FROM foo; +-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" FROM foo; --- name: SubqueryWithHavingClause :many -SELECT a, (SELECT COUNT(a) FROM foo GROUP BY b HAVING COUNT(a) > 10) as "total" FROM foo; +-- In PostgreSQL, only count() returns 0 for empty table. +-- https://www.postgresql.org/docs/15/functions-aggregate.html +-- name: CountRowsEmptyTable :many +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo; diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/schema.sql b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/schema.sql new file mode 100644 index 0000000000..1119555fdf --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE foo (a int not NULL, b int); + +INSERT INTO foo VALUES (1, 2); +INSERT INTO foo VALUES (3, NULL); +INSERT INTO foo VALUES (4, 5); + +CREATE TABLE empty (a int not NULL, b int); diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/sqlc.json b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/sqlc.json index 6645ccbd1b..32ede07158 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/sqlc.json +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/sqlc.json @@ -6,7 +6,7 @@ "engine": "postgresql", "sql_package": "pgx/v5", "name": "querytest", - "schema": "query.sql", + "schema": "schema.sql", "queries": "query.sql" } ] diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/models.go b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/models.go index b29372db24..56915dca09 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/models.go +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/models.go @@ -8,6 +8,11 @@ import ( "database/sql" ) +type Empty struct { + A int32 + B sql.NullInt32 +} + type Foo struct { A int32 B sql.NullInt32 diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/query.sql.go b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/query.sql.go index 19d9c71d93..676be08441 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/query.sql.go +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/query.sql.go @@ -7,27 +7,30 @@ package querytest import ( "context" + "database/sql" ) -const subqueryWithHavingClause = `-- name: SubqueryWithHavingClause :many -SELECT a, (SELECT COUNT(a) FROM foo GROUP BY b HAVING COUNT(a) > 10) as "total" FROM foo +const countRowsEmptyTable = `-- name: CountRowsEmptyTable :many +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo ` -type SubqueryWithHavingClauseRow struct { +type CountRowsEmptyTableRow struct { A int32 - Total int64 + Count int64 } -func (q *Queries) SubqueryWithHavingClause(ctx context.Context) ([]SubqueryWithHavingClauseRow, error) { - rows, err := q.db.QueryContext(ctx, subqueryWithHavingClause) +// In PostgreSQL, only count() returns 0 for empty table. +// https://www.postgresql.org/docs/15/functions-aggregate.html +func (q *Queries) CountRowsEmptyTable(ctx context.Context) ([]CountRowsEmptyTableRow, error) { + rows, err := q.db.QueryContext(ctx, countRowsEmptyTable) if err != nil { return nil, err } defer rows.Close() - var items []SubqueryWithHavingClauseRow + var items []CountRowsEmptyTableRow for rows.Next() { - var i SubqueryWithHavingClauseRow - if err := rows.Scan(&i.A, &i.Total); err != nil { + var i CountRowsEmptyTableRow + if err := rows.Scan(&i.A, &i.Count); err != nil { return nil, err } items = append(items, i) @@ -41,25 +44,57 @@ func (q *Queries) SubqueryWithHavingClause(ctx context.Context) ([]SubqueryWithH return items, nil } -const subqueryWithWhereClause = `-- name: SubqueryWithWhereClause :many -SELECT a, (SELECT COUNT(a) FROM foo WHERE a > 10) as "total" FROM foo +const firstRowFromEmptyTable = `-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" FROM foo ` -type SubqueryWithWhereClauseRow struct { +type FirstRowFromEmptyTableRow struct { A int32 - Total int64 + First sql.NullInt32 } -func (q *Queries) SubqueryWithWhereClause(ctx context.Context) ([]SubqueryWithWhereClauseRow, error) { - rows, err := q.db.QueryContext(ctx, subqueryWithWhereClause) +func (q *Queries) FirstRowFromEmptyTable(ctx context.Context) ([]FirstRowFromEmptyTableRow, error) { + rows, err := q.db.QueryContext(ctx, firstRowFromEmptyTable) if err != nil { return nil, err } defer rows.Close() - var items []SubqueryWithWhereClauseRow + var items []FirstRowFromEmptyTableRow for rows.Next() { - var i SubqueryWithWhereClauseRow - if err := rows.Scan(&i.A, &i.Total); err != nil { + var i FirstRowFromEmptyTableRow + if err := rows.Scan(&i.A, &i.First); 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 firstRowFromFooTable = `-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo +` + +type FirstRowFromFooTableRow struct { + A int32 + First sql.NullInt32 +} + +func (q *Queries) FirstRowFromFooTable(ctx context.Context) ([]FirstRowFromFooTableRow, error) { + rows, err := q.db.QueryContext(ctx, firstRowFromFooTable) + if err != nil { + return nil, err + } + defer rows.Close() + var items []FirstRowFromFooTableRow + for rows.Next() { + var i FirstRowFromFooTableRow + if err := rows.Scan(&i.A, &i.First); err != nil { return nil, err } items = append(items, i) diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/query.sql b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/query.sql index d68a4cb80d..ace2ad2d77 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/query.sql +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/query.sql @@ -1,7 +1,10 @@ -CREATE TABLE foo (a int not null, b int); +-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo; --- name: SubqueryWithWhereClause :many -SELECT a, (SELECT COUNT(a) FROM foo WHERE a > 10) as "total" FROM foo; +-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" FROM foo; --- name: SubqueryWithHavingClause :many -SELECT a, (SELECT COUNT(a) FROM foo GROUP BY b HAVING COUNT(a) > 10) as "total" FROM foo; +-- In PostgreSQL, only count() returns 0 for empty table. +-- https://www.postgresql.org/docs/15/functions-aggregate.html +-- name: CountRowsEmptyTable :many +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo; diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/schema.sql b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/schema.sql new file mode 100644 index 0000000000..1119555fdf --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE foo (a int not NULL, b int); + +INSERT INTO foo VALUES (1, 2); +INSERT INTO foo VALUES (3, NULL); +INSERT INTO foo VALUES (4, 5); + +CREATE TABLE empty (a int not NULL, b int); diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/sqlc.json b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/sqlc.json index c72b6132d5..f717ca2e66 100644 --- a/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/sqlc.json +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/sqlc.json @@ -5,7 +5,7 @@ "path": "go", "engine": "postgresql", "name": "querytest", - "schema": "query.sql", + "schema": "schema.sql", "queries": "query.sql" } ] diff --git a/internal/endtoend/testdata/nullable_subselect/sqlite/go/models.go b/internal/endtoend/testdata/nullable_subselect/sqlite/go/models.go index 48867fc1af..2f12b94418 100644 --- a/internal/endtoend/testdata/nullable_subselect/sqlite/go/models.go +++ b/internal/endtoend/testdata/nullable_subselect/sqlite/go/models.go @@ -8,6 +8,11 @@ import ( "database/sql" ) +type Empty struct { + A int64 + B sql.NullInt64 +} + type Foo struct { A int64 B sql.NullInt64 diff --git a/internal/endtoend/testdata/nullable_subselect/sqlite/go/query.sql.go b/internal/endtoend/testdata/nullable_subselect/sqlite/go/query.sql.go index d6591d63ef..2fc2360220 100644 --- a/internal/endtoend/testdata/nullable_subselect/sqlite/go/query.sql.go +++ b/internal/endtoend/testdata/nullable_subselect/sqlite/go/query.sql.go @@ -7,26 +7,30 @@ package querytest import ( "context" + "database/sql" ) -const subqueryWithHavingClause = `-- name: SubqueryWithHavingClause :many -SELECT a, (SELECT COUNT(a) FROM foo GROUP BY b HAVING COUNT(a) > 10) FROM foo +const countRowsEmptyTable = `-- name: CountRowsEmptyTable :many + +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo ` -type SubqueryWithHavingClauseRow struct { +type CountRowsEmptyTableRow struct { A int64 Count int64 } -func (q *Queries) SubqueryWithHavingClause(ctx context.Context) ([]SubqueryWithHavingClauseRow, error) { - rows, err := q.db.QueryContext(ctx, subqueryWithHavingClause) +// In SQLite, count() and total() return 0 for empty table. +// https://www.sqlite.org/lang_aggfunc.html +func (q *Queries) CountRowsEmptyTable(ctx context.Context) ([]CountRowsEmptyTableRow, error) { + rows, err := q.db.QueryContext(ctx, countRowsEmptyTable) if err != nil { return nil, err } defer rows.Close() - var items []SubqueryWithHavingClauseRow + var items []CountRowsEmptyTableRow for rows.Next() { - var i SubqueryWithHavingClauseRow + var i CountRowsEmptyTableRow if err := rows.Scan(&i.A, &i.Count); err != nil { return nil, err } @@ -41,25 +45,89 @@ func (q *Queries) SubqueryWithHavingClause(ctx context.Context) ([]SubqueryWithH return items, nil } -const subqueryWithWhereClause = `-- name: SubqueryWithWhereClause :many -SELECT a, (SELECT COUNT(a) FROM foo WHERE a > 10) FROM foo +const firstRowFromEmptyTable = `-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" FROM foo ` -type SubqueryWithWhereClauseRow struct { +type FirstRowFromEmptyTableRow struct { A int64 - Count int64 + First sql.NullInt64 } -func (q *Queries) SubqueryWithWhereClause(ctx context.Context) ([]SubqueryWithWhereClauseRow, error) { - rows, err := q.db.QueryContext(ctx, subqueryWithWhereClause) +func (q *Queries) FirstRowFromEmptyTable(ctx context.Context) ([]FirstRowFromEmptyTableRow, error) { + rows, err := q.db.QueryContext(ctx, firstRowFromEmptyTable) if err != nil { return nil, err } defer rows.Close() - var items []SubqueryWithWhereClauseRow + var items []FirstRowFromEmptyTableRow for rows.Next() { - var i SubqueryWithWhereClauseRow - if err := rows.Scan(&i.A, &i.Count); err != nil { + var i FirstRowFromEmptyTableRow + if err := rows.Scan(&i.A, &i.First); 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 firstRowFromFooTable = `-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo +` + +type FirstRowFromFooTableRow struct { + A int64 + First sql.NullInt64 +} + +func (q *Queries) FirstRowFromFooTable(ctx context.Context) ([]FirstRowFromFooTableRow, error) { + rows, err := q.db.QueryContext(ctx, firstRowFromFooTable) + if err != nil { + return nil, err + } + defer rows.Close() + var items []FirstRowFromFooTableRow + for rows.Next() { + var i FirstRowFromFooTableRow + if err := rows.Scan(&i.A, &i.First); 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 totalEmptyTable = `-- name: TotalEmptyTable :many +SELECT a, (SELECT total(a) FROM empty) as "total" FROM foo +` + +type TotalEmptyTableRow struct { + A int64 + Total float64 +} + +func (q *Queries) TotalEmptyTable(ctx context.Context) ([]TotalEmptyTableRow, error) { + rows, err := q.db.QueryContext(ctx, totalEmptyTable) + if err != nil { + return nil, err + } + defer rows.Close() + var items []TotalEmptyTableRow + for rows.Next() { + var i TotalEmptyTableRow + if err := rows.Scan(&i.A, &i.Total); err != nil { return nil, err } items = append(items, i) diff --git a/internal/endtoend/testdata/nullable_subselect/sqlite/query.sql b/internal/endtoend/testdata/nullable_subselect/sqlite/query.sql index 3c0edf6ab9..b378a40440 100644 --- a/internal/endtoend/testdata/nullable_subselect/sqlite/query.sql +++ b/internal/endtoend/testdata/nullable_subselect/sqlite/query.sql @@ -1,7 +1,14 @@ -CREATE TABLE foo (a int not null, b int); +-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo; --- name: SubqueryWithWhereClause :many -SELECT a, (SELECT COUNT(a) FROM foo WHERE a > 10) FROM foo; +-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" FROM foo; --- name: SubqueryWithHavingClause :many -SELECT a, (SELECT COUNT(a) FROM foo GROUP BY b HAVING COUNT(a) > 10) FROM foo; +-- In SQLite, count() and total() return 0 for empty table. +-- https://www.sqlite.org/lang_aggfunc.html + +-- name: CountRowsEmptyTable :many +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo; + +-- name: TotalEmptyTable :many +SELECT a, (SELECT total(a) FROM empty) as "total" FROM foo; diff --git a/internal/endtoend/testdata/nullable_subselect/sqlite/schema.sql b/internal/endtoend/testdata/nullable_subselect/sqlite/schema.sql new file mode 100644 index 0000000000..1119555fdf --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/sqlite/schema.sql @@ -0,0 +1,7 @@ +CREATE TABLE foo (a int not NULL, b int); + +INSERT INTO foo VALUES (1, 2); +INSERT INTO foo VALUES (3, NULL); +INSERT INTO foo VALUES (4, 5); + +CREATE TABLE empty (a int not NULL, b int); diff --git a/internal/endtoend/testdata/nullable_subselect/sqlite/sqlc.json b/internal/endtoend/testdata/nullable_subselect/sqlite/sqlc.json index fcb288cb35..cd66df063b 100644 --- a/internal/endtoend/testdata/nullable_subselect/sqlite/sqlc.json +++ b/internal/endtoend/testdata/nullable_subselect/sqlite/sqlc.json @@ -5,8 +5,8 @@ "path": "go", "engine": "sqlite", "name": "querytest", - "schema": "query.sql", + "schema": "schema.sql", "queries": "query.sql" } ] -} \ No newline at end of file +} diff --git a/internal/endtoend/testdata/select_exists/sqlite/go/query.sql.go b/internal/endtoend/testdata/select_exists/sqlite/go/query.sql.go index 7d5429c946..22faa2ea67 100644 --- a/internal/endtoend/testdata/select_exists/sqlite/go/query.sql.go +++ b/internal/endtoend/testdata/select_exists/sqlite/go/query.sql.go @@ -7,6 +7,7 @@ package querytest import ( "context" + "database/sql" ) const barExists = `-- name: BarExists :one @@ -21,9 +22,9 @@ SELECT ) ` -func (q *Queries) BarExists(ctx context.Context, id int64) (int64, error) { +func (q *Queries) BarExists(ctx context.Context, id int64) (sql.NullInt64, error) { row := q.db.QueryRowContext(ctx, barExists, id) - var column_1 int64 + var column_1 sql.NullInt64 err := row.Scan(&column_1) return column_1, err } diff --git a/internal/endtoend/testdata/select_nested_count/mysql/go/query.sql.go b/internal/endtoend/testdata/select_nested_count/mysql/go/query.sql.go index 7f81cf5d3d..c84f8081b1 100644 --- a/internal/endtoend/testdata/select_nested_count/mysql/go/query.sql.go +++ b/internal/endtoend/testdata/select_nested_count/mysql/go/query.sql.go @@ -22,7 +22,7 @@ type GetAuthorsWithBooksCountRow struct { ID int64 Name string Bio sql.NullString - BooksCount sql.NullInt64 + BooksCount int64 } func (q *Queries) GetAuthorsWithBooksCount(ctx context.Context) ([]GetAuthorsWithBooksCountRow, error) { diff --git a/internal/endtoend/testdata/select_nested_count/sqlite/go/query.sql.go b/internal/endtoend/testdata/select_nested_count/sqlite/go/query.sql.go index 7f81cf5d3d..c84f8081b1 100644 --- a/internal/endtoend/testdata/select_nested_count/sqlite/go/query.sql.go +++ b/internal/endtoend/testdata/select_nested_count/sqlite/go/query.sql.go @@ -22,7 +22,7 @@ type GetAuthorsWithBooksCountRow struct { ID int64 Name string Bio sql.NullString - BooksCount sql.NullInt64 + BooksCount int64 } func (q *Queries) GetAuthorsWithBooksCount(ctx context.Context) ([]GetAuthorsWithBooksCountRow, error) {