A while back, I was able to convince my team to use sqlc.
I already used it on some personal projects, but those weren’t complex queries whatsoever. Using sqlc in a commercial setting allowed me to see some of its limitations, or perhaps the shift in the paradigm that you experience while using it.
In this article, I want to show you some hacks I found to be useful.
Configuring the project
First things first, if you have a lot of packages, here’s a cool trick on how to make sure similar config is reused accross different queries:
|
|
As you can see on line 8, we are using yaml anchors to reuse the schema configuration. This is a neat little trick, which is actually vanilla yaml.
We are also going to employ the same trick to streamline go gen options config. Say we are are writing something that works with money. Using type overrides we can save ourselves from writing useless mappers. Instead, sqlc will automatically map db types and generate type safe code for us.
We needn’t worry about out:
being different, because we are overriding it on line 32 (in each gen.go.out
entry).
Make sure your type implements appropriate interfaces.
And you can also add some more overrides for custom types for specific domain (custom currency type or another enum).
For example if you use go-enum, you could do something like this.
|
|
--sql
flag is used to generate sql Scanner
/Valuer
compatible enums.
There are also some emit_*
and omit_*
config flags I find to be convenient. You can see the full list here in config .
You can also use the whole folder as queries param as such:
queries: internal/account/repository/
This way you can have multiple files with queries in one folder, and sqlc will generate code for all of them. It may help with keeping your codebase clean and organized.
Queries
Dynamic update params
Sometimes we need to write a query that is a bit more complex. For example, we need to apply some filters dynamically. There is a great article that describes it in more detail.
TL;DR: You can use @param::boolean
to conditionally apply filters.
This might look like this in repository layer:
|
|
Dynamic where clause
array_length(@param, 1)
checks the first dimension of the array@param
. Our array is always 1-dimensional anyway.- If the array is
NULL
or empty,array_length
returnsNULL
and the second part afterOR
is ignored. - This ensures the condition on the right
param = ANY(@param)
is omitted completely when the parameter is not provided.
This pattern allows you to write queries where filters can be dynamically applied only when needed.
Left join with embed
There is a known issue open at the time of writing: sqlc.embed() generates code that cannot handle NULL items.
Let’s say we have a query that joins two tables and we want to embed the result in a struct. We can use sqlc.embed()
to generate the code for us that will reuse the same structure.
This won’t work ^-^
But there is a known workaround. We can just create a view that will guarantee to have ID
fields, and the rest of those can be NULL
.
Migration:
query.sql:
Read also
Heavily inspired by: