Repository navigation
Description
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?andany-in?returnfalsewhen the relevant searched set is empty.all-in? #{...empty...} allowedreturnstrue(vacuous truth).none-in? #{...empty...} blockedreturnstrue.
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:
- Directly in query text as EDN set literals.
- Through named prepared-query slots, e.g. a set-valued
BindValue. - From a query variable bound to a stored
Value::Setor tocollect-setoutput.
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-setonly 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-setis a value-producing expression, not a Boolean predicate; it uses no?suffix and binds one set value.collect-setaccepts 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-setreturns 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?, andnone-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
docs(roadmap): move v3.0.0-milestone features into v3.0.0 scope (#733)
docs(spec): #316 address review — set lifecycle, element boundaries, …
Merge pull request #732 from project-minigraf/design/316-set-values
feat(values): set values end to end (#316)
test(values): set values unit, integration and model tests; docs (#316)
fix(storage): a long string and a long set with equal bytes keep thei…
test(wal): set size and keyword-element checks (#316)
Merge pull request #741 from project-minigraf/feat/316-set-values
Merge pull request #743 from project-minigraf/feat/316-set-predicates
Merge origin/v3 into feat/306-limit-op
Merge pull request #747 from project-minigraf/feat/316-collect-set
adityamukho commentedon Oct 24, 2026
Done on v3: set values (#741), set predicates (#743), collect-set (#747). Docs site and binding updates follow as PR 4.
Merge pull request #751 from project-minigraf/docs/316-pr4-plan
Merge pull request #753 from project-minigraf/feat/316-browser-set-bind
Merge pull request #754 from project-minigraf/docs/316-done