Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions internal/compiler/output_columns.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package compiler
import (
"errors"
"fmt"
"strings"

"github.com/sqlc-dev/sqlc/internal/config"
"github.com/sqlc-dev/sqlc/internal/sql/ast"
Expand Down Expand Up @@ -349,11 +350,15 @@ func (c *Compiler) outputColumns(qc *QueryCatalog, node ast.Node) ([]*Column, er
if err != nil {
return nil, err
}
first := subcols[0]
first := *subcols[0]
if res.Name != nil {
first.Name = *res.Name
}
cols = append(cols, first)
// A scalar subquery that matches no row yields NULL
if !yieldsOneRow(n.Subselect) {
first.NotNull = false
}
cols = append(cols, &first)
default:
cols = append(cols, &Column{Name: name, DataType: "any", NotNull: false})
}
Expand Down Expand Up @@ -810,3 +815,52 @@ func findColumnForRef(ref *ast.ColumnRef, tables []*Table, targetList *ast.List)

return nil
}

// aggregates names the aggregate functions that return a row even when no row
// matches, as count(*) does
var aggregates = map[string]bool{
"count": true, "sum": true, "avg": true, "min": true, "max": true,
"bool_and": true, "bool_or": true, "every": true, "array_agg": true,
"string_agg": true, "json_agg": true, "jsonb_agg": true,
"json_object_agg": true, "jsonb_object_agg": true,
}

// yieldsOneRow reports whether a scalar subquery always yields exactly one row:
// a SELECT with no FROM or WHERE, or one whose every column is an aggregate,
// with no GROUP BY, HAVING, LIMIT, OFFSET or set operation.
func yieldsOneRow(node ast.Node) bool {
sel, ok := node.(*ast.SelectStmt)
if !ok || sel.Op != ast.None || given(sel.LimitCount) || given(sel.LimitOffset) || given(sel.HavingClause) {
return false
}
if sel.GroupClause != nil && len(sel.GroupClause.Items) > 0 {
return false
}
if (sel.FromClause == nil || len(sel.FromClause.Items) == 0) && !given(sel.WhereClause) {
return true
}
if sel.TargetList == nil || len(sel.TargetList.Items) == 0 {
return false
}
for _, item := range sel.TargetList.Items {
res, ok := item.(*ast.ResTarget)
if !ok {
return false
}
call, ok := res.Val.(*ast.FuncCall)
if !ok || call.Func == nil || call.Over != nil || !aggregates[strings.ToLower(call.Func.Name)] {
return false
}
}
return true
}

// given reports whether a clause was written: the parser leaves one that wasn't
// as nil or as a placeholder
func given(n ast.Node) bool {
if n == nil {
return false
}
_, placeholder := n.(*ast.TODO)
return !placeholder
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://github.com/sqlc-dev/sqlc/issues/4262

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- name: ThroughView :many
select id, review_id, reviews from book_view;

-- name: Inline :many
select (select r.id from reviews r where r.book_id = b.id limit 1) review_id,
(select count(*) from reviews r where r.book_id = b.id) reviews,
(select 1) one
from books b;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
create table books (id int primary key, title text not null);
create table reviews (id int primary key, book_id int not null);
create view book_view as
select b.id, (select r.id from reviews r where r.book_id = b.id limit 1) review_id,
(select count(*) from reviews r where r.book_id = b.id) reviews
from books b;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: "2"
sql:
- engine: "postgresql"
schema: "schema.sql"
queries: "query.sql"
gen:
go:
package: "querytest"
out: "go"
sql_package: "database/sql"
Loading