Omitting not-null parameters inserts zero value instead of an error #120
Replies: 2 comments 2 replies
-
The example code you provided is type-safe and correct. The default zero value for the ID field is the empty string, which according to your table definition is a valid value. If empty string is not valid, I'd suggest adding a column constraint. CREATE TABLE foo (
id VARCHAR(27) NOT NULL idchk (char_length > 0),
val INTEGER NOT NULL,
); Zero values are not NULL values and I think it's a mistake to try and equate the two. I've ported a few code bases to sqlc and have not run into this issue. I think the convenience of struct params outweighs the occasional forgotten parameter. That said, I do understand this is an issue for larger code bases, so let’s look over your two suggestions in reverse oder. I really do not like positional arguments for methods with many parameters. Beyond one argument, they become difficult to use. For your example table, it would be easy to mix up parameters when calling query.CreateFoo(ctx, "", 0) I also really dislike the “everything is a pointer” approach taken by the AWS SDK. Pointers to strings, ints, and bools are hard to create without helper methods. I think the only option I like is the type CreateFooParams struct {
ID sql.NullString
Val sql.NullInt32
}
query.CreateFoo(ctx, query.CreateFooParams{
Val: sql.NullInt32{Int32: 42, Valid: true},
}) Constructing the query.CreateFoo(ctx, query.CreateFooParams{
ID: null.String("some-id"),
Val: null.Int32(42),
}) I know this solution looks basically the same as the pointer solution, but I like that it's explicit about expected zero value of fields. The only downside to this approach is that it doesn't work well with types that do not have an associated |
Beta Was this translation helpful? Give feedback.
-
I just ran into this issue in a project using tables with I think @kyleconroy has the right solution for types that map to For types outside of |
Beta Was this translation helpful? Give feedback.
-
sqlc promises type-safety in its generated code. Unfortunately it runs into the same problem that all Go programs do which is the inability to distinguish between a non-present value and a zero value.
I defined a table like so:
An insert statement like so:
And then called it like so:
I expect that this call will fail. In a perfect world it would fail at compile-time. It a better world it would fail at runtime when the database says "you tried to insert a row but did not specify the NOT NULL parameter
id
.What I observed is that the call succeeds and that the database simply contains an empty string for the
id
value.Mitigations:
There are two reasonable mitigations I can imagine, but they both have tradeoffs:
(these could be columns like
sql.NullString
instead of pointer values if you'd like, it makes little difference)And then provide convenience functions to make the interface less gross. This is the approach taken by the AWS libraries. e.g.
or if it's generated using
sql.NullInt32
:Presumably this could be a tunable option that callers could choose on a query-by-query basis with another argument on the query (and could be set globally in
sqlc.json
as well):This makes the call site far less readable, especially when working with many parameters but it enforces the type-safe at compile time which is nice. sqlc currently uses a heuristic that it takes that approach if there is a single argument (and generates a structure if there are 2+ arguments). It's possible that sqlc could make that a tunable argument of the query, e.g.
If you specify
struct
, it generates a struct of all parameters, but if you specifyargs
it generates a function call where each parameter is a separate argument to the function. This would allow the library user to trade off safety for readability.Beta Was this translation helpful? Give feedback.
All reactions