Skip to content

Commit 4026f85

Browse files
committed
feat: rewrite list queries with comment-marker placeholders
Replaces the sqlc.narg(filter)::void and cursor machinery in List queries with SQL comment placeholders (/* query.cursor AND */, /* query.filter AND */, /* query.order , */) that a runtime rewriter (e.g. sqlc-gen-template) substitutes before execution. When left unreplaced they remain SQL comments, so the query runs as an offset-paginated scan ordered by primary key. Removes page_start from the generator; keeps page_order for the default ORDER BY tie-breaker.
1 parent 0fc7229 commit 4026f85

3 files changed

Lines changed: 40 additions & 68 deletions

File tree

internal/sqlc/generator.go

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,12 @@ func (x *Generator) Generate() error {
110110
}
111111
return condition.String()
112112
},
113+
"query_argument": func(column Column) string {
114+
argument := Argument{
115+
Column: &column,
116+
}
117+
return argument.String()
118+
},
113119
"query_index": func(index *Index) string {
114120
// Don't add suffix for primary key lookups
115121
if index.Name == "primary key" {
@@ -123,32 +129,16 @@ func (x *Generator) Generate() error {
123129
}
124130
return "By" + strings.Join(items, "And")
125131
},
126-
"query_argument": func(column Column) string {
127-
argument := Argument{
128-
Column: &column,
129-
}
130-
return argument.String()
131-
},
132132
// Pagination functions
133-
"page_start": func(table Table) string {
134-
if table.PrimaryKey != nil && len(table.PrimaryKey.Parts) > 0 {
135-
argument := &Argument{
136-
Column: &Column{
137-
Name: "page_start",
138-
Type: "text",
139-
Null: true,
140-
},
141-
}
142-
column := table.PrimaryKey.Parts[0].Column
143-
return fmt.Sprintf("(%v::text IS NULL OR %s::text > %v::text)", argument, column, argument)
133+
"query_order": func(table Table) string {
134+
if table.PrimaryKey == nil {
135+
return ""
144136
}
145-
return ""
146-
},
147-
"page_order": func(table Table) string {
148-
if table.PrimaryKey != nil && len(table.PrimaryKey.Parts) > 0 {
149-
return table.PrimaryKey.Parts[0].Column
137+
cols := make([]string, 0, len(table.PrimaryKey.Parts))
138+
for _, p := range table.PrimaryKey.Parts {
139+
cols = append(cols, p.Column)
150140
}
151-
return ""
141+
return strings.Join(cols, ", ")
152142
},
153143
// Foreign key index check
154144
"is_fk_index": func(table Table, index *Index) bool {

internal/sqlc/template/template.sql.tmpl

Lines changed: 26 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -267,78 +267,61 @@ INSERT INTO {{.Table.Name}} (
267267

268268
-- List{{table_name .Table.Name "many"}} retrieves a paginated list of rows from '{{$.Table.Name}}'.
269269
--
270-
-- Filtering:
271-
-- The 'filter' parameter enables dynamic WHERE clauses for flexible querying.
272-
-- Pass NULL for no filtering, or use a query builder (e.g., pgxmql.WhereClause)
273-
-- to construct runtime conditions. The void type cast allows query rewriting.
274-
-- Example: filter can add conditions like "status = 'active' AND created_at > '2024-01-01'"
270+
-- Filtering, cursor, and ordering:
271+
-- The commented markers in WHERE and ORDER BY are placeholders for a runtime
272+
-- query rewriter (e.g. sqlc-gen-template) to substitute cursor, filter, and
273+
-- order expressions. When left unreplaced they remain SQL comments, so the
274+
-- query returns all rows ordered by primary key.
275275
--
276-
-- Pagination:
277-
-- Cursor-based: Use page_start (ID of last item from previous page) + page_limit.
278-
-- Returns items AFTER the cursor. Set page_start to the last item's ID from current page as next cursor.
279-
-- Offset-based: Use page_offset + page_limit for traditional page number pagination.
276+
-- Offset pagination:
277+
-- Use offset + limit for traditional page number pagination.
280278
-- name: {{$query_name}} :many
281279
SELECT
282280
*
283281
FROM
284282
{{$.Table.Name}}
285-
{{- $page_start := page_start $.Table}}
286-
{{- $page_order := page_order $.Table}}
287283
WHERE
288-
sqlc.narg(filter)::void IS NULL
289-
{{- if $page_start}}
290-
AND {{$page_start}}
291-
{{- end}}
292-
{{- if $page_order}}
284+
/* query.cursor AND */ /* query.filter AND */ TRUE
285+
{{- $query_order := query_order $.Table}}
286+
{{- if $query_order}}
293287
ORDER BY
294-
{{$page_order}}
288+
/* query.order , */ {{$query_order}}
295289
{{- end}}
296290
LIMIT
297-
sqlc.narg(page_limit)::int
291+
sqlc.narg(limit)::int
298292
OFFSET
299-
sqlc.narg(page_offset)::int;
293+
sqlc.narg(offset)::int;
300294

301295
{{range $idx, $key := .Table.GetNonUniqueIndexes}}{{- $query_name := printf "List%s%s" (table_name $.Table.Name "many") (query_index $key)}}
302296
{{- if or (is_fk_index $.Table $key) (should_include $ $query_name)}}
303297

304298
-- List{{table_name $.Table.Name "many"}}{{query_index $key}} retrieves a paginated list of rows from '{{$.Table.Name}}'{{if $key.Name}} filtered by {{$key.Name}}{{end}}.
305299
--
306-
-- Filtering:
307-
-- The 'filter' parameter enables dynamic WHERE clauses for flexible querying.
308-
-- Pass NULL for no filtering, or use a query builder (e.g., pgxmql.WhereClause)
309-
-- to construct runtime conditions. The void type cast allows query rewriting.
310-
-- Example: filter can add conditions like "status = 'active' AND created_at > '2024-01-01'"
300+
-- Filtering, cursor, and ordering:
301+
-- The commented markers in WHERE and ORDER BY are placeholders for a runtime
302+
-- query rewriter (e.g. sqlc-gen-template) to substitute cursor, filter, and
303+
-- order expressions. When left unreplaced they remain SQL comments, so the
304+
-- query returns all matching rows ordered by primary key.
311305
--
312-
-- Pagination:
313-
-- Cursor-based: Use page_start (ID of last item from previous page) + page_limit.
314-
-- Returns items AFTER the cursor. Set page_start to the last item's ID from current page as next cursor.
315-
-- Offset-based: Use page_offset + page_limit for traditional page number pagination.
306+
-- Offset pagination:
307+
-- Use offset + limit for traditional page number pagination.
316308
-- name: {{$query_name}} :many
317309
SELECT
318310
*
319311
FROM
320312
{{$.Table.Name}}
321313
{{- $condition := query_condition $.Table $key}}
322-
{{- $page_start := page_start $.Table}}
323-
{{- $page_order := page_order $.Table}}
324-
{{- if $condition}}
325-
WHERE
326-
{{$condition}}
327-
{{- if $page_start}}
328-
AND {{$page_start}}
329-
{{- end}}
330-
{{- else if $page_start}}
331314
WHERE
332-
{{$page_start}}
333-
{{- end}}
334-
{{- if $page_order}}
315+
/* query.cursor AND */ /* query.filter AND */ {{if $condition}}{{$condition}}{{else}}TRUE{{end}}
316+
{{- $query_order := query_order $.Table}}
317+
{{- if $query_order}}
335318
ORDER BY
336-
{{$page_order}}
319+
/* query.order , */ {{$query_order}}
337320
{{- end}}
338321
LIMIT
339-
sqlc.narg(page_limit)::int
322+
sqlc.narg(limit)::int
340323
OFFSET
341-
sqlc.narg(page_offset)::int;
324+
sqlc.narg(offset)::int;
342325
{{- end}}
343326

344327
{{- $query_name := printf "Update%s%s" (table_name $.Table.Name "many") (query_index $key)}}

internal/sqlc/template/template_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ var _ = Describe("Template", func() {
2222
"query_argument": func(args ...any) string { return "" },
2323
"query_index": func(args ...any) string { return "" },
2424
// Pagination Functions
25-
"page_start": func(args ...any) string { return "" },
26-
"page_order": func(args ...any) string { return "" },
25+
"query_order": func(args ...any) string { return "" },
2726
// Foreign key index check
2827
"is_fk_index": func(args ...any) bool { return false },
2928
// Query include function

0 commit comments

Comments
 (0)