You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
SQLC does not infer nullability for subqueries in view SELECT #4262
CREATE VIEW schema.vw_myview AS
SELECT
p.id AS id_produto,
(SELECT i.name
FROM public.tb_ibpt i
WHERE ((i.code)::text = (p.ncm)::text)
LIMIT 1) AS description,
...
I would expect that selecting the description column in the resulting vw_myview would create a nullable type in the SQLC generated struct, but instead a string field is created. So when there are no matches in the subquery, there is a scan error.
This also happens outside views, and with CASE whose ELSE is a typed null (CASE WHEN … THEN s.label ELSE NULL::text END, which is how pg_dump writes every CASE without ELSE). On 1.31.1 and main (3ad8b20), the legacy analyzer marks both non-null. With SQLCEXPERIMENT=coreanalyzer both are nullable, as they should be, but left-joined columns become non-null there instead.
Minimal repro:
-- schema.sqlcreatetableauthors (id intprimary key, name textnot null);
createtableseries (id intprimary key, name textnot null);
createtablebooks (id intprimary key, title textnot null, author_id intnot nullreferences authors, series_id int);
createtablereviews (id intprimary key, book_id intnot null);
createtableshelves (id intprimary key, label textnot null, book_id int);
createviewbook_viewasselectb.id, (selectr.idfrom reviews r wherer.book_id=b.id) review_id,
case when b.id>0 then s.label else null::text end shelf,
(selectcount(*) from reviews r wherer.book_id=b.id) reviews
from books b left join shelves s ons.book_id=b.id;
-- query.sql-- name: LeftJoin :manyselectb.title, s.namefrom books b left join series s ons.id=b.series_id;
-- name: ThroughView :manyselect id, review_id, shelf, reviews from book_view;
-- name: Inline :manyselect (selectr.idfrom reviews r wherer.book_id=b.id) review_id,
case when b.id>0 then b.title else null::text end maybe_title
from books b;
Nullability in the codegen request (? = nullable):
legacy
coreanalyzer
LeftJoin.name
text? ✓
text ✗
ThroughView.review_id (scalar subquery)
int4 ✗
integer? ✓
ThroughView.shelf (ELSE NULL::text)
text ✗
text? ✓
ThroughView.reviews (count(*) subquery)
bigint ✓
bigint? (always one row, so could be non-null)
Inline.review_id
int4 ✗
integer? ✓
Inline.maybe_title
text ✗
text? ✓
I'd like to send a fix to internal/compiler/output_columns.go for the legacy path: scalar subqueries and ELSE NULL::type nullable, (SELECT count(*) …) kept non-null. Would you take that, or would you rather fixes go to the core analyzer?
Activity
ken-talltree-io commentedon Oct 24, 2026
This also happens outside views, and with
CASEwhoseELSEis a typed null (CASE WHEN … THEN s.label ELSE NULL::text END, which is howpg_dumpwrites everyCASEwithoutELSE). On 1.31.1 andmain(3ad8b20), the legacy analyzer marks both non-null. WithSQLCEXPERIMENT=coreanalyzerboth are nullable, as they should be, but left-joined columns become non-null there instead.Minimal repro:
Nullability in the codegen request (
?= nullable):coreanalyzerLeftJoin.nametext?✓text✗ThroughView.review_id(scalar subquery)int4✗integer?✓ThroughView.shelf(ELSE NULL::text)text✗text?✓ThroughView.reviews(count(*)subquery)bigint✓bigint?(always one row, so could be non-null)Inline.review_idint4✗integer?✓Inline.maybe_titletext✗text?✓I'd like to send a fix to
internal/compiler/output_columns.gofor the legacy path: scalar subqueries andELSE NULL::typenullable,(SELECT count(*) …)kept non-null. Would you take that, or would you rather fixes go to the core analyzer?