diff --git a/internal/compiler/output_columns.go b/internal/compiler/output_columns.go index dbdbe252b3..6f6232e79c 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/ast" "github.com/sqlc-dev/sqlc/internal/sql/astutils" @@ -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,6 +384,12 @@ 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") || + strings.EqualFold(first.FuncName, "total"))) { + first.NotNull = false + } + cols = append(cols, first) default: diff --git a/internal/compiler/query.go b/internal/compiler/query.go index b3cf9d6154..068142803e 100644 --- a/internal/compiler/query.go +++ b/internal/compiler/query.go @@ -29,6 +29,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/db.go b/internal/endtoend/testdata/nullable_subselect/mysql/go/db.go new file mode 100644 index 0000000000..fb6ae669f6 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/mysql/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.19.1 + +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/nullable_subselect/mysql/go/models.go b/internal/endtoend/testdata/nullable_subselect/mysql/go/models.go new file mode 100644 index 0000000000..56915dca09 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/mysql/go/models.go @@ -0,0 +1,19 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.19.1 + +package querytest + +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 new file mode 100644 index 0000000000..d6d4ec0158 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/mysql/go/query.sql.go @@ -0,0 +1,109 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.19.1 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const countRowsEmptyTable = `-- name: CountRowsEmptyTable :many +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo +` + +type CountRowsEmptyTableRow struct { + A int32 + Count int64 +} + +// 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 []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.Close(); err != nil { + return nil, err + } + 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 FirstRowFromEmptyTableRow struct { + A int32 + First sql.NullInt32 +} + +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 []FirstRowFromEmptyTableRow + for rows.Next() { + 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) + } + 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/nullable_subselect/mysql/query.sql b/internal/endtoend/testdata/nullable_subselect/mysql/query.sql new file mode 100644 index 0000000000..9489d1abbb --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/mysql/query.sql @@ -0,0 +1,10 @@ +-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo; + +-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" 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 new file mode 100644 index 0000000000..e41c39e8b3 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/mysql/sqlc.json @@ -0,0 +1,12 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "mysql", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/db.go b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/db.go new file mode 100644 index 0000000000..f0ea7fd21a --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.0 + +package querytest + +import ( + "context" + + "github.com/jackc/pgconn" + "github.com/jackc/pgx/v4" +) + +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/nullable_subselect/postgresql/pgx/v4/go/models.go b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/models.go new file mode 100644 index 0000000000..852556161d --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/models.go @@ -0,0 +1,19 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.0 + +package querytest + +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 new file mode 100644 index 0000000000..a398b4f071 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/go/query.sql.go @@ -0,0 +1,100 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.0 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +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 FirstRowFromEmptyTableRow struct { + A int32 + First sql.NullInt32 +} + +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 []FirstRowFromEmptyTableRow + for rows.Next() { + 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) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/query.sql b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/query.sql new file mode 100644 index 0000000000..ace2ad2d77 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/query.sql @@ -0,0 +1,10 @@ +-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo; + +-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" 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 new file mode 100644 index 0000000000..d1244c9e7a --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v4/sqlc.json @@ -0,0 +1,13 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "postgresql", + "sql_package": "pgx/v4", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/db.go b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/db.go new file mode 100644 index 0000000000..e715a0f400 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.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/nullable_subselect/postgresql/pgx/v5/go/models.go b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/models.go new file mode 100644 index 0000000000..b51b97d52c --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/models.go @@ -0,0 +1,19 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.0 + +package querytest + +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 new file mode 100644 index 0000000000..f878f4e192 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/go/query.sql.go @@ -0,0 +1,101 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.0 +// source: query.sql + +package querytest + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +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 FirstRowFromEmptyTableRow struct { + A int32 + First pgtype.Int4 +} + +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 []FirstRowFromEmptyTableRow + for rows.Next() { + 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 pgtype.Int4 +} + +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) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/query.sql b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/query.sql new file mode 100644 index 0000000000..ace2ad2d77 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/query.sql @@ -0,0 +1,10 @@ +-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo; + +-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" 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 new file mode 100644 index 0000000000..32ede07158 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/pgx/v5/sqlc.json @@ -0,0 +1,13 @@ +{ + "version": "1", + "packages": [ + { + "path": "go", + "engine": "postgresql", + "sql_package": "pgx/v5", + "name": "querytest", + "schema": "schema.sql", + "queries": "query.sql" + } + ] +} diff --git a/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/db.go b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/db.go new file mode 100644 index 0000000000..57406b68e8 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.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/nullable_subselect/postgresql/stdlib/go/models.go b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/models.go new file mode 100644 index 0000000000..852556161d --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/models.go @@ -0,0 +1,19 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.0 + +package querytest + +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 new file mode 100644 index 0000000000..13cc71504c --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/go/query.sql.go @@ -0,0 +1,109 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.0 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +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.QueryContext(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.Close(); err != nil { + return nil, err + } + 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 FirstRowFromEmptyTableRow struct { + A int32 + First sql.NullInt32 +} + +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 []FirstRowFromEmptyTableRow + for rows.Next() { + 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) + } + 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/nullable_subselect/postgresql/stdlib/query.sql b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/query.sql new file mode 100644 index 0000000000..ace2ad2d77 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/query.sql @@ -0,0 +1,10 @@ +-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo; + +-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" 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 new file mode 100644 index 0000000000..f717ca2e66 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/postgresql/stdlib/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/nullable_subselect/sqlite/go/db.go b/internal/endtoend/testdata/nullable_subselect/sqlite/go/db.go new file mode 100644 index 0000000000..57406b68e8 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/sqlite/go/db.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.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/nullable_subselect/sqlite/go/models.go b/internal/endtoend/testdata/nullable_subselect/sqlite/go/models.go new file mode 100644 index 0000000000..d5028ecbc0 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/sqlite/go/models.go @@ -0,0 +1,19 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.0 + +package querytest + +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 new file mode 100644 index 0000000000..33e5cd8cf4 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/sqlite/go/query.sql.go @@ -0,0 +1,142 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.20.0 +// source: query.sql + +package querytest + +import ( + "context" + "database/sql" +) + +const countRowsEmptyTable = `-- name: CountRowsEmptyTable :many + +SELECT a, (SELECT count(a) FROM empty) as "count" FROM foo +` + +type CountRowsEmptyTableRow struct { + A int64 + Count int64 +} + +// 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 []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.Close(); err != nil { + return nil, err + } + 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 FirstRowFromEmptyTableRow struct { + A int64 + First sql.NullInt64 +} + +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 []FirstRowFromEmptyTableRow + for rows.Next() { + 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) + } + 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/nullable_subselect/sqlite/query.sql b/internal/endtoend/testdata/nullable_subselect/sqlite/query.sql new file mode 100644 index 0000000000..b378a40440 --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/sqlite/query.sql @@ -0,0 +1,14 @@ +-- name: FirstRowFromFooTable :many +SELECT a, (SELECT a FROM foo limit 1) as "first" FROM foo; + +-- name: FirstRowFromEmptyTable :many +SELECT a, (SELECT a FROM empty limit 1) as "first" 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 new file mode 100644 index 0000000000..cd66df063b --- /dev/null +++ b/internal/endtoend/testdata/nullable_subselect/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/select_exists/sqlite/go/query.sql.go b/internal/endtoend/testdata/select_exists/sqlite/go/query.sql.go index b4f00584ec..cba10d3a55 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 }