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
EntityPrimaryKeyInSetentry") - 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 evitaqlafter modifyingEvitaQL.g4. The generated files inevita_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, benchmarkedgetOperationCost()viaFormulaCostMeasurement.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.mdfor the full annotation framework (@ConstraintDefinition,@Creator,@Classifier,@Value,@Child,@AdditionalChild). @ConstraintSupportedValuesvs Creator parameter type: ThesupportedTypesin the annotation controls which types the external APIs (GraphQL/REST) advertise in their schemas. The@Creatorconstructor parameter type is whatConstraintProcessor.resolveValueParameter()reads viaparameter.getType()and stores as theValueParameterDescriptortype — the GraphQL schema builder then maps this to a GraphQL scalar viaDataTypesConverter.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 inDataTypesConverter.SCALAR_MAPPINGSand crashes the entire GraphQL schema build. To accept broader numeric types, keep thesupportedTypeslist broad (e.g.{ Byte.class, Short.class, Integer.class, Long.class, BigDecimal.class }) — the external API layer handles type coercion viaEvitaDataTypes.toTargetTypebefore 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 atInteger.MAX_VALUE/Integer.MIN_VALUE. ReturnEmptyFormula.INSTANCEwhen no value can satisfy the constraint (e.g.greaterThan(Integer.MAX_VALUE)→ empty).