8000 feat(query): add set membership predicates · Issue #316 · project-minigraf/minigraf · GitHub
[go: up one dir, main page]

Skip to content
7FFF

feat(query): add set membership predicates #316

Closed

Description

@adityamukho

Problem

Minigraf query expressions operate only on scalar Values. A caller cannot express membership against a query literal or a prepared-query bind value, forcing application-side query construction or repeated or clauses.

The existing contains? predicate is string-specific, so it must retain its current substring semantics.

Proposed query surface

Add first-class EDN set literals (#{...}), a corresponding persisted Value::Set, and a prepared-query bind representation. Follow the current lower-case kebab-case predicate convention and retain the trailing ? for Boolean predicates.

;; scalar belongs to an allowed set
[(in? ?role #{:role/admin :role/editor})]

;; scalar does not belong to a blocked set
[(not-in? ?status #{:status/banned :status/deleted})]

;; two-set predicates: first collection tested against second collection
[(any-in? ?requested #{:scope/read :scope/write})]
[(all-in? ?requested #{:scope/read :scope/write :scope/admin})]
[(none-in? ?requested #{:scope/suspended :scope/revoked})]

Required semantics:

Predicate Meaning
in? value set value is a member of set.
not-in? value set value is not a member of set.
any-in? values allowed The two sets intersect.
all-in? values allowed values is a subset of allowed.
none-in? values blocked The two sets are disjoint.

Empty-set behavior follows standard set theory:

  • in? and any-in? return false when the relevant searched set is empty.
  • all-in? #{...empty...} allowed returns true (vacuous truth).
  • none-in? #{...empty...} blocked returns true.

not-in? is included because scalar exclusion is common, readable, and avoids requiring an additional general Boolean-negation expression feature. Do not add aliases such as contains?, only-in?, or not-all-in?; they are ambiguous, redundant, or conflict with the existing string predicate.

Inputs

The predicates must accept sets supplied either:

  1. Directly in query text as EDN set literals.
  2. Through named prepared-query slots, e.g. a set-valued BindValue.
  3. From a query variable bound to a stored Value::Set or to collect-set output.

Set elements should use Minigraf's existing sc 7438 alar Value domain. Type mismatches (a scalar supplied where a set is required) should follow the existing expression convention: the binding does not pass the filter rather than coercing values. Sets have unique elements and equality is independent of insertion order.

Multi-valued attributes

Retain ordinary triple-pattern semantics: [?e :user/scope ?scope] emits one binding per matching fact. It must never be implicitly lifted into a set.

Add an explicit value-producing collection-binding expression, without a ? suffix:

[(collect-set ?e :user/scope) ?scopes]
[(all-in? $requested ?scopes)]

collect-set requires a bound entity and concrete attribute. It returns the deduplicated set of net-asserted values for that (entity, attribute) pair in the query's current temporal view, respecting :as-of, :valid-at, retractions, and the query snapshot. When no matching facts exist, it binds the empty set instead of dropping the already-bound entity row.

collect-set is shallow and scalar-only in this feature. If a matching attribute value is itself a collection, the query must return a clear execution error; it must neither silently omit that value nor deep-flatten it. A stored set-valued attribute should instead be bound directly through an ordinary triple pattern and passed to a membership predicate.

This supports both data-model choices:

  • Store a set as one atomic attribute value when the collection has one shared temporal history.
  • Store repeated attribute values as independent facts when each membership needs independent temporal history, indexing, or querying; call collect-set only when set-level comparison is required.

Acceptance criteria

  • #{...} parses as an EDN set literal without breaking tagged literals.
  • Set values can be transacted, persisted, queried, returned, and supplied through prepared-query bindings.
  • Query literals, stored set values, collect-set, and prepared-query bindings can produce a set expression value.
  • collect-set is a value-producing expression, not a Boolean predicate; it uses no ? suffix and binds one set value.
  • collect-set accepts only a bound entity and concrete attribute, observes the query's temporal view, deduplicates values, and returns an empty set for a missing attribute.
  • collect-set returns a clear execution error when any collected value is a collection; it never silently omits or implicitly flattens collection values.
  • in?, not-in?, any-in?, all-in?, and none-in? parse as built-in predicates and work in both filter and Boolean-binding expression forms.
  • Set membership works for every supported scalar value type, including keywords and refs.
  • Duplicate elements are deduplicated; equality and output are deterministic.
  • Empty-set behavior matches the table above.
  • Invalid operand shapes do not coerce values and behave consistently with existing predicate type mismatches.
  • Parser, executor, storage/file-format compatibility, prepared-statement, documentation, grammar-conformance, and property/edge-case tests cover the feature.
  • contains? remains a string-only substring predicate.

Out of scope

  • General list/vector/map values.
  • Nested sets or other recursive collection types.
  • General-purpose aggregation or arbitrary relation-to-set operations beyond collect-set.
  • Index-specific optimization beyond existing predicate pushdown.

Future nested-collection design

If a future feature introduces nested collection values, collect-set must remain shallow. Nested values should retain their boundaries and be treated as atomic values for structural equality, rather than being implicitly traversed. Any traversal must be introduced as a separate explicit operation (for example, flatten or deep-flatten) with independently specified depth, ordering, deduplication, null, and error semantics.

Activity

  1. added this to the milestone on Aug 24, 2026
  2. modified the milestones: , v3.1.0 on Sep 26, 2026
  3. modified the milestones: v3.1.0, v3.0.0 on Oct 9, 2026
  4. adityamukho commented on Oct 24, 2026

    @adityamukho
    CollaboratorAuthor

    Done on v3: set values (#741), set predicates (#743), collect-set (#747). Docs site and binding updates follow as PR 4.

  5. added 3 commits that reference this issue on Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0