Documentation
¶
Overview ¶
Package ent is the Ent kind of the store tier (store.KindEnt): the Ent/SQL backend of the list-query compiler — it interprets the list-query IR into Ent dialect/sql predicates and order options, the func(*sql.Selector) shape every Ent query accepts via query.Where(predicate.T(pred)) and query.Order(opt) — plus Apply and the FoldFilters/FoldOrder glue an Ent store's list adapters share. It is the only package that imports entgo.io/ent, so the Ent dependency stays confined to this kind while the shared fold (foundation/listquery) and the algebra (core/interfaces) stay ORM-free.
Index ¶
- func Apply(fmap *listquery.Map, filter types.Filter, sort []types.OrderField, ...) (predicate func(*entsql.Selector), order []func(*entsql.Selector))
- func FoldFilters(filters []types.Filter) []func(*entsql.Selector)
- func FoldOrder(order []types.OrderField) []func(*entsql.Selector)
- type Backend
- func (Backend) And(subs []EntPredicate) EntPredicate
- func (Backend) Clause(col string, op types.FilterOperator, v any) EntPredicate
- func (Backend) Empty() EntPredicate
- func (Backend) ExistsClause(join types.JoinTarget, op types.FilterOperator, v any) EntPredicate
- func (Backend) Or(subs []EntPredicate) EntPredicate
- func (Backend) Order(col string, desc bool) EntPredicate
- func (Backend) OrderDerived(d types.DerivedSort, desc bool) EntPredicate
- func (Backend) OrderJoin(join types.JoinTarget, desc bool) EntPredicate
- func (Backend) OrderOrdinal(col string, values []string, desc bool) EntPredicate
- type EntPredicate
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Apply ¶
func Apply( fmap *listquery.Map, filter types.Filter, sort []types.OrderField, tiebreaker string, ) (predicate func(*entsql.Selector), order []func(*entsql.Selector))
Apply compiles a validated DSL filter + sort into the Ent selector-level predicate and order-option funcs, resolving each field's storage column via the resource's allow-list (fmap). It is the shared convenience every wired list store uses: the returned predicate is AND-ed into the store's authz-scoped Ent query, and the order funcs (carrying the resource keyset column then id as tiebreakers) are converted to the entity's OrderOption and spread into .Order(...). tiebreaker is the resource's keyset column (from ResourceSchema.KeysetColumn — created_at for most resources, joined_at for a membership join table); an empty tiebreaker falls back to created_at. An empty filter compiles to a no-op predicate.
func FoldFilters ¶
FoldFilters folds resolved-column filters to Ent predicates with an identity resolver — a ListSpec's filters (user filter + keyset seek) already carry resolved columns, so no allow-list is consulted.
func FoldOrder ¶
func FoldOrder(order []types.OrderField) []func(*entsql.Selector)
FoldOrder folds the resolved ORDER BY columns to Ent sort options via the same backend. A joined sort field carries a JoinTarget instead of a base column, rendered as an ORDER BY over a correlated subquery; a derived field carries an aggregate/interval spec, rendered as a computed expression; an ordinal field renders as a CASE over its values; a same-table field renders as a plain ORDER BY.
Types ¶
type Backend ¶
type Backend struct{}
Backend interprets the list-query IR into parameterized Ent predicates and order options. It is stateless — construct it with New — and satisfies interfaces.QueryBackend[EntPredicate, EntPredicate].
func (Backend) And ¶
func (Backend) And( subs []EntPredicate, ) EntPredicate
And combines sub-predicates so that every one must match, via sql.AndPredicates — the combinator over the func(*sql.Selector) predicate shape (sql.And operates on *sql.Predicate, a different type, so it cannot compose these).
func (Backend) Clause ¶
func (Backend) Clause(col string, op types.FilterOperator, v any) EntPredicate
Clause interprets a single leaf comparison into a parameterized Ent predicate. The column comes only from the allow-list (never client input) and the value is always a bound parameter, so the result is injection-safe. Values arrive already type-validated by the listquery parser; a value whose Go type does not fit the operator (only reachable from a hand-built clause) degrades to a no-op predicate rather than panicking.
func (Backend) Empty ¶
func (Backend) Empty() EntPredicate
Empty is the no-op (match-all) predicate for an absent or empty filter.
func (Backend) ExistsClause ¶
func (Backend) ExistsClause( join types.JoinTarget, op types.FilterOperator, v any, ) EntPredicate
ExistsClause interprets a leaf comparison on a JOINED table's column into a correlated EXISTS predicate: the base row matches when a row in join.Table — correlated by join.TargetKey = <base>.join.LocalKey — satisfies op/v on one of join.Columns (ORed when >1). It adds NO join to the base query, so the base scope/soft-delete predicates and keyset order are untouched; when join.TargetSoftDeletes is set the subquery also excludes soft-deleted target rows. A $like match folds case when join.CaseInsensitive is set (ILIKE via ContainsFold) and is case-sensitive otherwise. A value whose Go type does not fit the operator degrades to a match-all (add-nothing) predicate, mirroring Clause.
func (Backend) Or ¶
func (Backend) Or( subs []EntPredicate, ) EntPredicate
Or combines sub-predicates so that at least one must match, via sql.OrPredicates.
func (Backend) Order ¶
func (Backend) Order(col string, desc bool) EntPredicate
Order interprets one sort directive into an Ent order option (ORDER BY col ASC/DESC) as a func(*sql.Selector), the shape query.Order accepts.
func (Backend) OrderDerived ¶
func (Backend) OrderDerived(d types.DerivedSort, desc bool) EntPredicate
OrderDerived interprets a COMPUTED / CORRELATED sort directive into an ORDER BY over an expression that is not a base column — exactly one arm of d is set:
- d.Aggregate: a correlated aggregate over a child table, `ORDER BY (SELECT COUNT(*) FROM child WHERE child.target_key = base.local_key [AND child.deleted_at IS NULL]) <dir>` (or MAX/MIN of a column). It backs sorting a computed count that is not a base column — a connector's Documents count, a team grant's Members count — and never joins the base query, exactly like OrderJoin.
- d.Interval: a timestamp-plus-interval, `ORDER BY (base.base_col + CASE base.discriminator WHEN 'v0' THEN make_interval(secs=>n0) … ELSE NULL END) <dir>` — a connector's Next Sync = last_sync_at
- the schedule's interval, computed in SQL so it can never drift from a stored copy.
NULLS placement is direction-aware (LAST asc / FIRST desc) so a NULL derived value (a soft-deleted-only aggregate is 0, but an unmatched interval case or a NULL base timestamp yields NULL) sorts predictably. The interval seconds + discriminator values are TRUSTED spec DATA (from the store's derived-sort resolver, never client input, like a column name), so they are inlined as SQL literals — the same safety argument as OrderOrdinal. A malformed spec (neither arm set) degrades to a no-op sort node.
func (Backend) OrderJoin ¶
func (Backend) OrderJoin(join types.JoinTarget, desc bool) EntPredicate
OrderJoin interprets a joined sort directive into an ORDER BY over a correlated scalar subquery per join column: `ORDER BY (SELECT t.col FROM t WHERE t.target_key = base.local_key [AND t.deleted_at IS NULL] LIMIT 1) <dir> NULLS <LAST|FIRST>`. All joins are N:1 with a non-null FK, so the subquery yields one row — and NULL only when the sole target row is soft-deleted: the join.TargetSoftDeletes guard excludes it (mirroring ExistsClause/OrderDerived), so a soft-deleted target never contributes a STALE value to the sort and instead sorts as NULL, hence the explicit, direction-aware NULLS clause. Without the guard the filter (ExistsClause) and the sort would be asymmetric on the same soft-deleted target. A multi-column join (member "name") emits one ORDER BY term per column (first_name, then last_name); the correlated PK lookup is cheap (EXPLAIN evidence), so the doubled lookup is acceptable and a single LATERAL is not required. It never joins the base query.
func (Backend) OrderOrdinal ¶
func (Backend) OrderOrdinal(col string, values []string, desc bool) EntPredicate
OrderOrdinal interprets a value-ordinal sort directive into `ORDER BY CASE col WHEN 'v0' THEN 0 WHEN 'v1' THEN 1 … ELSE <len> END <dir>`, so an enum column sorts by the declared value order (e.g. access_level admin<write<read = owner<editor<viewer) rather than alphabetically. An unlisted value sorts last (the ELSE <len> bucket). Empty values falls back to a plain column order (defensive).
The values are inlined as quoted SQL literals (with ” escaping), NOT bound parameters: they are TRUSTED proto DATA from the (listquery.field).column.sort_ordinal annotation — never client input, exactly like the column name — so inlining is safe, and it avoids the placeholder-numbering collision that arises when an ORDER BY expression's bound args interleave with the WHERE clause's args.
type EntPredicate ¶
EntPredicate is the Ent query-node type: a function that mutates a *sql.Selector to add a WHERE predicate or an ORDER BY term. Ent unifies filter and sort as this one shape, so the backend uses it for both the predicate type Q and the sort type S of the QueryBackend algebra.