ent

package
v0.0.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: MIT Imports: 6 Imported by: 0

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

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

func FoldFilters(filters []types.Filter) []func(*entsql.Selector)

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 New

func New() Backend

New returns the Ent query-compiler backend.

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

type EntPredicate = func(*entsql.Selector)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL