| name | new-constraint |
| description | Plans and executes implementation of a new evitaDB query constraint (filter, order, or require). Produces a task checklist covering all layers — query model, EvitaQL grammar, engine translator, Kryo serializer, external APIs, tests, and documentation — with exact file paths. Use when adding new filter/order/require constraints or variants of existing ones. |
| allowed-tools | Read, Glob, Grep, Bash(rg *), Bash(find *), Bash(ls *), Bash(wc *), Write, Agent |
Planning New Constraint
Produces an implementation plan for adding a new query constraint to evitaDB.
Procedure
Step 1: Gather requirements
Ask the user (if not already clear):
- Constraint type: filter, order, or require
- What it does: behavior, parameters, return semantics
- Supported domains: ENTITY, REFERENCE, INLINE_REFERENCE, FACET, etc.
- Does it need a new formula? Or reuses existing infrastructure
Step 2: Find the closest existing constraint
Search the codebase for the most similar existing constraint to use as a template:
| If the new constraint is... | Study this template |
|---|
| Filter leaf, entity property | EntityPrimaryKeyInSet, EntityPrimaryKeyGreaterThan |
| Filter leaf, attribute comparison | AttributeGreaterThan, AttributeBetween |
| Filter leaf, price | PriceBetween, PriceInCurrency |
| Filter container | And, ReferenceHaving, HierarchyWithin |
| Order leaf | EntityPrimaryKeyNatural, AttributeNatural |
| Require leaf | Page, PriceContent |
| Require container | EntityFetch, ReferenceContent |
Step 3: Trace the template through all layers
For the chosen template, search for every file that references it. This reveals the exact set of files the new constraint must touch:
rg "TemplateConstraintName" --type java -l
Cross-reference the results against the full checklist in LAYERS.md to ensure nothing is missed.
Step 4: Produce the plan
For each item in the checklist from LAYERS.md:
- Provide the exact file path to create or modify
- Specify which template file to mimic (the one from Step 2)
- For modifications, note where in the file to add (e.g., "after the
EntityPrimaryKeyInSet entry")
- Include brief notes on any deviations from the template pattern
Step 5: Save the plan
Write to docs/plans/YYYY-MM-DD-{constraint-name}.md with this structure:
# [Constraint Name] Implementation Plan
**Goal:** [One sentence]
**Template constraint:** [Name of template being followed]
**Architecture:** [2-3 sentences about approach]
---
## Task N: [Layer/component name]
**Files:**
- Create: `exact/path/to/NewConstraint.java` (template: `exact/path/to/TemplateConstraint.java`)
- Modify: `exact/path/to/Registry.java` (add after `TemplateConstraint.class`)
**Key differences from template:**
- [What's different about the new constraint vs the template]
Critical rules
- Kryo registration: ALWAYS append at the END of
QuerySerializationKryoConfigurer.java. Inserting in the middle shifts class IDs and breaks deserialization of existing stored data.
- Grammar regeneration: Run
cd evita_query && ./generate_grammar.sh evitaql after modifying EvitaQL.g4. The generated files in evita_query/.../parser/grammar/ are checked into version control.
- Formula framework: New formulas MUST follow
documentation/developer/formula/formula_framework.md — lazy evaluation, unique CLASS_ID, benchmarked getOperationCost() via FormulaCostMeasurement.java (COST 1 = 1M ops/s).
- GraphQL/REST: Usually automatic from constraint descriptors. Manual API code only needed for container constraints with additional children.
- gRPC: No changes needed — passes EvitaQL strings.
- Tests are domain-grouped: E2E tests live in
evita_test/.../functional/{entity,attribute,price,hierarchy,facet,fetch}/ — place the test in the matching domain package.
- Constraint descriptor annotations: See
documentation/developer/query/query_constraint_description_framework.md for the full annotation framework (@ConstraintDefinition, @Creator, @Classifier, @Value, @Child, @AdditionalChild).
@ConstraintSupportedValues vs Creator parameter type: The supportedTypes in the annotation controls which types the external APIs (GraphQL/REST) advertise in their schemas. The @Creator constructor parameter type is what ConstraintProcessor.resolveValueParameter() reads via parameter.getType() and stores as the ValueParameterDescriptor type — the GraphQL schema builder then maps this to a GraphQL scalar via DataTypesConverter.getGraphQLScalarType. The Creator parameter must be a concrete type (e.g. Integer, String), never a generic like <T extends Number & Serializable>, because Java type erasure reduces the generic to its first bound (Number.class), which has no mapping in DataTypesConverter.SCALAR_MAPPINGS and crashes the entire GraphQL schema build. To accept broader numeric types, keep the supportedTypes list broad (e.g. { Byte.class, Short.class, Integer.class, Long.class, BigDecimal.class }) — the external API layer handles type coercion via EvitaDataTypes.toTargetType before the constraint constructor is called.
- Integer overflow in strict comparison translators: When translating strict inequality constraints (e.g.
greaterThan → [threshold + 1, MAX_VALUE]), always guard against overflow at Integer.MAX_VALUE / Integer.MIN_VALUE. Return EmptyFormula.INSTANCE when no value can satisfy the constraint (e.g. greaterThan(Integer.MAX_VALUE) → empty).