feat(count): add QueryCount pagination helpers

Adds CountResult, QueryCount, BuildQueryCountFn, and ReadableTable
alias for Jet pagination total counts, with tests and docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-29 18:25:49 -07:00
parent 08f8387856
commit 689e022e3e
6 changed files with 74 additions and 1 deletions
+1
View File
@@ -15,6 +15,7 @@
- `StringKSUID.Equal` / `IsZero`, `BinaryKSUID.Equal` / `IsZero`, and nil sentinel vars for [ApplyInterfacePtr]. - `StringKSUID.Equal` / `IsZero`, `BinaryKSUID.Equal` / `IsZero`, and nil sentinel vars for [ApplyInterfacePtr].
- `Query`, `MustQuery`, and `UpdateOne` helpers (+ Context variants). - `Query`, `MustQuery`, and `UpdateOne` helpers (+ Context variants).
- `WithTxValue` for transactional functions that return a value. - `WithTxValue` for transactional functions that return a value.
- `QueryCount`, `BuildQueryCountFn`, and `CountResult` for pagination counts.
- Context-aware variants for all query and mutation helpers. - Context-aware variants for all query and mutation helpers.
- `Delete`, `DeleteAffected`, `WithTx`, `ContainsCol`, and `CurrentDialect()`. - `Delete`, `DeleteAffected`, `WithTx`, `ContainsCol`, and `CurrentDialect()`.
- Package documentation (`doc.go`), expanded README, and subpackage docs for `dbxm` / `dbxp`. - Package documentation (`doc.go`), expanded README, and subpackage docs for `dbxm` / `dbxp`.
+1 -1
View File
@@ -45,7 +45,7 @@ For Postgres, use `dbx.DialectPostgres` and blank-import `dbxp` instead of `dbxm
| Query | `Fetch`, `MustFetch`, `FetchOne`, `MustFetchOne`, `Query`, `MustQuery` (+ `*Context` variants) | | Query | `Fetch`, `MustFetch`, `FetchOne`, `MustFetchOne`, `Query`, `MustQuery` (+ `*Context` variants) |
| Mutations | `Insert`, `InsertReturning`, `Update`, `UpdateAffected`, `UpdateOne`, `UpdateReturning`, `Delete`, `DeleteAffected` (+ `*Context` variants) | | Mutations | `Insert`, `InsertReturning`, `Update`, `UpdateAffected`, `UpdateOne`, `UpdateReturning`, `Delete`, `DeleteAffected` (+ `*Context` variants) |
| Transactions | `WithTx`, `WithTxValue` | | Transactions | `WithTx`, `WithTxValue` |
| Columns | `NormalCols`, `ContainsCol`, `ExprValues`, `ExprStringers` | | Columns | `NormalCols`, `ContainsCol`, `ExprValues`, `ExprStringers`, `QueryCount`, `BuildQueryCountFn` |
| Partial update | `ApplyPtr`, `ApplyComplexPtr`, `ApplyInterfacePtr`, `ApplyVal` | | Partial update | `ApplyPtr`, `ApplyComplexPtr`, `ApplyInterfacePtr`, `ApplyVal` |
| Pointers | `Ptr`, `Val`, `NowPtr`, `TrimPtr`, `TrimPtrToNil`, `IsZero` | | Pointers | `Ptr`, `Val`, `NowPtr`, `TrimPtr`, `TrimPtrToNil`, `IsZero` |
| Types | `StringKSUID`, `BinaryKSUID`, `JSONB` | | Types | `StringKSUID`, `BinaryKSUID`, `JSONB` |
+44
View File
@@ -0,0 +1,44 @@
package dbx
import (
"fmt"
"github.com/go-jet/jet/v2/mysql"
)
// CountResult holds the count from a Jet COUNT query. Use with a SELECT that
// aliases the count as `CountResult.Count`, for example:
//
// SELECT(mysql.COUNT(col).AS("CountResult.Count"))
type CountResult struct {
Count int
}
// QueryCountFn counts rows matching pre-bound table and condition parameters.
type QueryCountFn func(sqlo Queryable) (int, error)
// QueryCount counts rows in tbl matching conds.
func QueryCount(
sqlo Queryable,
col Column,
tbl ReadableTable,
conds BoolExpression,
) (int, error) {
stmt := tbl.SELECT(mysql.COUNT(col).AS("CountResult.Count")).WHERE(conds)
var res CountResult
if err := stmt.Query(sqlo, &res); err != nil {
return 0, fmt.Errorf("query count: %w", err)
}
return res.Count, nil
}
// BuildQueryCountFn returns a QueryCountFn with col, tbl, and conds bound.
func BuildQueryCountFn(
col Column,
tbl ReadableTable,
conds BoolExpression,
) QueryCountFn {
return func(sqlo Queryable) (int, error) {
return QueryCount(sqlo, col, tbl, conds)
}
}
+24
View File
@@ -0,0 +1,24 @@
package dbx
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCountResult_Scan(t *testing.T) {
stmt := mockStatement{
queryContextFn: func(_ context.Context, dest any) error {
ptr := dest.(*CountResult)
ptr.Count = 3
return nil
},
}
var scanned CountResult
err := stmt.Query(mockQueryable{}, &scanned)
require.NoError(t, err)
assert.Equal(t, 3, scanned.Count)
}
+1
View File
@@ -17,6 +17,7 @@
// //
// Dialect-neutral type aliases ([Column], [ColumnList]) and helpers for column // Dialect-neutral type aliases ([Column], [ColumnList]) and helpers for column
// lists ([NormalCols], [ContainsCol]) and expression building ([ExprValues]). // lists ([NormalCols], [ContainsCol]) and expression building ([ExprValues]).
// [QueryCount] and [BuildQueryCountFn] support pagination total counts.
// //
// Partial-update helpers ([ApplyPtr], [ApplyVal]) track changed fields for // Partial-update helpers ([ApplyPtr], [ApplyVal]) track changed fields for
// repository patch logic. // repository patch logic.
+3
View File
@@ -13,3 +13,6 @@ type Expression = mysql.Expression
// BoolExpression is a dialect-neutral alias for Jet boolean expressions. // BoolExpression is a dialect-neutral alias for Jet boolean expressions.
type BoolExpression = mysql.BoolExpression type BoolExpression = mysql.BoolExpression
// ReadableTable is a dialect-neutral alias for Jet readable tables.
type ReadableTable = mysql.ReadableTable