diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d9b0ef..410c4e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - `StringKSUID.Equal` / `IsZero`, `BinaryKSUID.Equal` / `IsZero`, and nil sentinel vars for [ApplyInterfacePtr]. - `Query`, `MustQuery`, and `UpdateOne` helpers (+ Context variants). - `WithTxValue` for transactional functions that return a value. +- `QueryCount`, `BuildQueryCountFn`, and `CountResult` for pagination counts. - Context-aware variants for all query and mutation helpers. - `Delete`, `DeleteAffected`, `WithTx`, `ContainsCol`, and `CurrentDialect()`. - Package documentation (`doc.go`), expanded README, and subpackage docs for `dbxm` / `dbxp`. diff --git a/README.md b/README.md index 2c7843c..3242d35 100644 --- a/README.md +++ b/README.md @@ -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) | | Mutations | `Insert`, `InsertReturning`, `Update`, `UpdateAffected`, `UpdateOne`, `UpdateReturning`, `Delete`, `DeleteAffected` (+ `*Context` variants) | | Transactions | `WithTx`, `WithTxValue` | -| Columns | `NormalCols`, `ContainsCol`, `ExprValues`, `ExprStringers` | +| Columns | `NormalCols`, `ContainsCol`, `ExprValues`, `ExprStringers`, `QueryCount`, `BuildQueryCountFn` | | Partial update | `ApplyPtr`, `ApplyComplexPtr`, `ApplyInterfacePtr`, `ApplyVal` | | Pointers | `Ptr`, `Val`, `NowPtr`, `TrimPtr`, `TrimPtrToNil`, `IsZero` | | Types | `StringKSUID`, `BinaryKSUID`, `JSONB` | diff --git a/count.go b/count.go new file mode 100644 index 0000000..418218a --- /dev/null +++ b/count.go @@ -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) + } +} diff --git a/count_test.go b/count_test.go new file mode 100644 index 0000000..63afb09 --- /dev/null +++ b/count_test.go @@ -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) +} diff --git a/doc.go b/doc.go index 1050b11..0270efa 100644 --- a/doc.go +++ b/doc.go @@ -17,6 +17,7 @@ // // Dialect-neutral type aliases ([Column], [ColumnList]) and helpers for column // lists ([NormalCols], [ContainsCol]) and expression building ([ExprValues]). +// [QueryCount] and [BuildQueryCountFn] support pagination total counts. // // Partial-update helpers ([ApplyPtr], [ApplyVal]) track changed fields for // repository patch logic. diff --git a/jet_types.go b/jet_types.go index 5e0c85b..fa4b06a 100644 --- a/jet_types.go +++ b/jet_types.go @@ -13,3 +13,6 @@ type Expression = mysql.Expression // BoolExpression is a dialect-neutral alias for Jet boolean expressions. type BoolExpression = mysql.BoolExpression + +// ReadableTable is a dialect-neutral alias for Jet readable tables. +type ReadableTable = mysql.ReadableTable