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
feat: Introduce sqlc.optional for dynamic query generation
This commit introduces the `sqlc.optional` feature, allowing conditional inclusion of SQL query fragments at runtime.
Key changes:
1. **Parser Enhancement**: The SQL parser now recognizes `sqlc.optional('ConditionKey', 'SQLFragment')` syntax within query files. This information is stored in the query's metadata.
2. **Code Generation**:
- Go code generation logic has been updated to process these `OptionalBlocks`.
- Generated Go functions now include new parameters (typed as `interface{}`) corresponding to each `ConditionKey`.
- Templates (`stdlib/queryCode.tmpl`, `pgx/queryCode.tmpl`) were modified to dynamically build the SQL query string and its arguments at runtime. If an optional Go parameter is non-nil, its associated SQL fragment is included in the final query, and its value is added to the list of database arguments.
3. **Parameter Handling**: `$N` placeholders in all SQL fragments (base or optional) consistently refer to the Nth parameter in the generated Go function's signature.
4. **Documentation**: Added comprehensive documentation for `sqlc.optional` in `docs/reference/query-annotations.md`, covering syntax, behavior, parameter numbering, and examples.
5. **Examples**: A new runnable example has been added to `examples/dynamic_query/postgresql/` to demonstrate practical usage.
6. **Tests**: New end-to-end tests were added in `internal/endtoend/testdata/dynamic_query/` for both `stdlib` and `pgx` drivers, ensuring the correctness of the generated code.
The `sqlc.optional` annotation allows for parts of a SQL query to be conditionally included at runtime. This is useful for building queries with optional filters or other dynamic components.
119
+
120
+
### Purpose
121
+
122
+
`sqlc.optional` provides a way to construct dynamic SQL queries where certain SQL fragments are only appended to the base query if a corresponding Go parameter is non-`nil`. This avoids the need for complex string manipulation or multiple similar queries for different filtering scenarios.
123
+
124
+
### Syntax
125
+
126
+
You include `sqlc.optional` calls directly in your SQL query comments, after the main query body. Each call specifies a key (which becomes part of the Go function parameter name) and the SQL fragment to include.
127
+
128
+
```sql
129
+
-- name: GetItemsByOwner :many
130
+
SELECT*FROM items
131
+
WHERE owner_id = $1-- Base condition for mandatory parameter
For each `sqlc.optional('Key', 'SQLFragment')` annotation, a new parameter is added to the generated Go function. The parameter name is derived from `Key` (converted to lowerCamelCase, e.g., `nameFilter`, `activeOnly`), and its type is `interface{}`.
139
+
140
+
Given the SQL example above, the generated Go function signature would be:
Here, `ownerID int64` is the standard parameter corresponding to `$1`. `nameFilter interface{}` and `activeOnly interface{}` are the optional parameters generated due to `sqlc.optional`.
147
+
148
+
### Runtime Behavior
149
+
150
+
- The SQL fragment associated with an `sqlc.optional` directive is appended to the main query (with a preceding space) if the corresponding Go parameter in the generated function is **not `nil`**.
151
+
- If the parameter is `nil`, the fragment is ignored.
152
+
- The database driver receives the fully constructed SQL string and only the parameters that are active (standard parameters + non-`nil` optional parameters).
153
+
154
+
### Parameter Numbering
155
+
156
+
The `$N` placeholders in *any* SQL fragment (whether part of the base query or an `sqlc.optional` fragment) **must** correspond to the position of the argument in the generated Go function's parameter list.
157
+
158
+
- Standard (non-optional) parameters are numbered first, based on their order in the SQL query.
159
+
- Optional parameters are numbered subsequently, based on the order of their `sqlc.optional` appearance in the SQL query.
160
+
161
+
**Example:**
162
+
163
+
For the query:
164
+
```sql
165
+
-- name: GetItemsByOwner :many
166
+
SELECT * FROM items
167
+
WHERE owner_id = $1 -- owner_id is the 1st parameter
168
+
sqlc.optional('NameFilter', 'AND name LIKE $2') -- nameFilter is the 2nd parameter
169
+
sqlc.optional('ActiveOnly', 'AND is_active = $3'); -- activeOnly is the 3rd parameter
- In the `NameFilter` fragment, `$2` refers to `nameFilter`.
177
+
- In the `ActiveOnly` fragment, `$3` refers to `activeOnly`.
178
+
179
+
If `nameFilter` is `nil` and `activeOnly` is provided, the final SQL sent to the driver might look like:
180
+
`SELECT * FROM items WHERE owner_id = $1 AND is_active = $2`
181
+
And the parameters passed to the driver would be `ownerID` and the value of `activeOnly`. The database driver sees a query with parameters re-numbered sequentially from `$1`. sqlc handles this re-numbering automatically when constructing the query for the driver.
182
+
183
+
### Complete Example
184
+
185
+
**SQL (`query.sql`):**
186
+
```sql
187
+
-- name: ListUsers :many
188
+
SELECT id, name, status FROM users
189
+
WHERE1=1-- Base condition (can be any valid SQL expression)
190
+
sqlc.optional('NameParam', 'AND name LIKE $1')
191
+
sqlc.optional('StatusParam', 'AND status = $2');
192
+
```
193
+
*(For this specific example, if `NameParam` is active, it's `$1`. If `StatusParam` is active, it's `$2`. If both are active, `NameParam` is `$1` and `StatusParam` is `$2` in their respective fragments, but they become `$1` and `$2` overall if no mandatory params precede them. The parameter numbering in fragments refers to their final position in the argument list passed to the database driver, which sqlc constructs based on active parameters.)*
194
+
195
+
**Correction to the above parenthetical note, aligning with the "Parameter Numbering" section:**
196
+
The `$N` in the SQL fragments refers to the Go function signature's parameter order.
197
+
-`NameParam` (if not nil) corresponds to `$1`.
198
+
-`StatusParam` (if not nil) corresponds to `$2`.
199
+
200
+
If `NameParam` is `John%` and `StatusParam` is `active`, the effective SQL is:
201
+
`SELECT id, name, status FROM users WHERE 1=1 AND name LIKE $1 AND status = $2`
202
+
And the parameters passed to the driver are `John%` and `active`.
203
+
204
+
If `NameParam` is `nil` and `StatusParam` is `active`, the effective SQL is:
205
+
`SELECT id, name, status FROM users WHERE 1=1 AND status = $1`
206
+
And the parameter passed to the driver is `active`. sqlc handles mapping the Go parameters to the correct positional placeholders for the final SQL.
This feature provides a powerful way to reduce boilerplate and manage complex queries with multiple optional conditions directly within your SQL files.
289
+
116
290
## `:batchexec`
117
291
118
292
__NOTE: This command only works with PostgreSQL using the `pgx/v4` and `pgx/v5` drivers and outputting Go code.__
0 commit comments