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
Postgres: casting result to text results in sqlc generating a string instead of a nullable string #3710
The problem stems from the fact that postgres casts allow null, while SQLC is assuming casts means NOT NULL.
I can see that on the parameter/argument side of things, sqlc has sqlc.narg() for helping clarify the situation.
However, on the generated struct side, there doesn't appear to be anything easy to help fix this situation.
I have also tried adding a case statement to try to make it clear to SQLC that the result can be null:
-- name: MyGet :manySELECT*, CASE WHEN mt.myjson @? '$.thing1.thing2' THEN (mt.myjson->'thing1'->'thing2')::text ELSE null::text END
FROM"mytable" mt;
When using ELSE null::text, the result is an output model using type string
When using either ELSE null or just leaving out the ELSE part, the result is an output model using type interface{}
I have also tried switching the type from a text to an enum, but the results are the same.
To be clear, my goal is to have the output model be:
hi, I have created PR to allow any selected column with json operator in type cast to be nullable column. let me know if you have any comments on that. is it working on your side?
-- name: GetNullable1 :manySELECTnull::textFROM"mytable";
-- name: GetNullable2 :manySELECT CASE WHEN true THEN 'hello'::text ELSE null::text END
FROM"mytable";
-- name: GetNullable3 :manySELECT CASE WHEN true THEN 'hello'::text ELSE null END
FROM"mytable";
-- name: GetNullable4 :manySELECT CASE WHEN true THEN 'hello'::text END
FROM"mytable";
-- name: GetNullable5 :manySELECT (mt.myjson->'thing1'->'thing2')::textFROM"mytable";
-- name: GetNullable6 :manySELECTmt.myjson->'thing1'->>'thing2'FROM"mytable";
The first (SELECT null::text) does give us []pgtype.Text
But the second one (SELECT CASE WHEN true THEN 'hello'::text ELSE null::text END) gives us []string
And the next two, (SELECT CASE WHEN true THEN 'hello'::text ELSE null END and SELECT CASE WHEN true THEN 'hello'::text END), give us []interface{}, which is bad too.
The last ones your PR might cover. (mt.myjson->'thing1'->'thing2')::text is returning []string incorrectly
And mt.myjson->'thing1'->>'thing2' is returning []interface{}
Those 6 above would be the test cases I would like added to this library. All should return []pgtype.Text
Reacted by Pulung Ragil, Jakub Proboszcz, Sjors Gielen and Mark
Really appreciate your feedback. I have added your test cases and few other test cases to make sure that it's really working. Let me know if you have any comments on the PR #3739
This use case is really important!
When sqlc cannot guess the correct type, there's no way around it. One can't even use type casts like ::text because it implicitly means "not null"!
Would be lovely if sqlc supported type casts with "nullable" hint
Rust's sqlx library handles this using the field names themselves. It is a little hacky but it allows defining a column as nullable or not by respectively naming it AS "column?"AS "column!". In that case it is needed because postgres' nullability logic isn't always flawless (coalesce returns nullable regardless of what you give it, for example).
Activity
veqryn commentedon Nov 16, 2024
I have tried changing my query to the json operator to specifically extract as text:
This results in an output model using
interface{}, which is just as bad:This is despite the docs saying that the
->>operator outputs text: https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-PROCESSINGI have also tried adding a case statement to try to make it clear to SQLC that the result can be null:
When using
ELSE null::text, the result is an output model using typestringWhen using either
ELSE nullor just leaving out the ELSE part, the result is an output model using typeinterface{}I have also tried switching the type from a text to an enum, but the results are the same.
To be clear, my goal is to have the output model be:
I definitely thought that
null::textshould have resulted in a nullable string being generated..., but it didn't.swallowstalker commentedon Dec 10, 2024
hi, I have created PR to allow any selected column with json operator in type cast to be nullable column. let me know if you have any comments on that. is it working on your side?
#3739
veqryn commentedon Dec 10, 2024
I think the problem is more than just JSON operators, though your PR would help quite a bit.
The following cases should all generate a function that returns
[]pgtype.Text.https://play.sqlc.dev/p/fba08f07ca2fed93313e4f005a64330c09b3722d78b868e94031ef774dc885a2
The first (
SELECT null::text) does give us[]pgtype.TextBut the second one (
SELECT CASE WHEN true THEN 'hello'::text ELSE null::text END) gives us[]stringAnd the next two, (
SELECT CASE WHEN true THEN 'hello'::text ELSE null ENDandSELECT CASE WHEN true THEN 'hello'::text END), give us[]interface{}, which is bad too.The last ones your PR might cover.
(mt.myjson->'thing1'->'thing2')::textis returning[]stringincorrectlyAnd
mt.myjson->'thing1'->>'thing2'is returning[]interface{}Those 6 above would be the test cases I would like added to this library. All should return
[]pgtype.Textswallowstalker commentedon Dec 12, 2024
Really appreciate your feedback. I have added your test cases and few other test cases to make sure that it's really working. Let me know if you have any comments on the PR #3739
kolypto commentedon Jan 20, 2026
This use case is really important!
When sqlc cannot guess the correct type, there's no way around it. One can't even use type casts like
::textbecause it implicitly means "not null"!Would be lovely if sqlc supported type casts with "nullable" hint
sidju commentedon Jul 30, 2026
Rust's sqlx library handles this using the field names themselves. It is a little hacky but it allows defining a column as nullable or not by respectively naming it
AS "column?"AS "column!". In that case it is needed because postgres' nullability logic isn't always flawless (coalesce returns nullable regardless of what you give it, for example).