Skip to content

feat: add where filter operator for arrays - #9

Merged
mzaqueu merged 2 commits into
mainfrom
zaqueu/where-clause
May 22, 2026
Merged

mzaqueu merged 2 commits into
mainfrom
zaqueu/where-clause

Conversation

@mzaqueu

@mzaqueu mzaqueu commented May 21, 2026 •

Copy link
Copy Markdown
Contributor

Add proprietary 'where' operator that filters arrays by a predicate before applying .length or a quantifier (any/all/none).

Examples:
selections where (odd ge 1.4).length ge 4
selections where (odd ge 1.4) any (provider eq "X")
selections where (odd ge 1.4) all (is_live eq true)
selections where (odd ge 1.4) none (is_fraud eq true)

The operator is implemented without materializing a filtered array:

  • 'where (P).length' uses a dedicated NodeWhereCount that counts elements matching the predicate, no allocation.
  • 'where (P) any/all/none (S)' is rewritten at parse time as a quantifier over the source with a composed predicate, reusing the existing zero-allocation quantifier evaluator.

Limitations:

  • No chained where (where (...) where (...)).
  • Only .length, any, all, none may follow where.
  • 'where' is a reserved keyword; contexts using a field named 'where' will conflict.
  • Not part of nikunjy/rules; rules using where won't run on that library.

📝 Summary

⚡ Performance Impact

  • No performance impact
  • Performance improved (include benchmarks)
  • Performance regression (justify why acceptable)

✅ Checklist

  • Code follows project style
  • Self-reviewed the code
  • Tests added for new functionality
  • README updated (if required)

Summary by CodeRabbit

  • New Features

    • Introduced a reserved "where" list-filter operator for array filtering, supporting .length and quantifiers (any/all/none) over filtered subsets.
  • Documentation

    • Added "List Operators" docs, examples, and compatibility notes demonstrating where, .length, and quantifiers and their usage constraints.
  • Tests & Examples

    • Added parser/evaluation tests, examples, and benchmark cases covering where usage and performance.

Review Change Stack

Add proprietary 'where' operator that filters arrays by a predicate before
applying .length or a quantifier (any/all/none).

Examples:
  selections where (odd ge 1.4).length ge 4
  selections where (odd ge 1.4) any (provider eq "X")
  selections where (odd ge 1.4) all (is_live eq true)
  selections where (odd ge 1.4) none (is_fraud eq true)

The operator is implemented without materializing a filtered array:
- 'where (P).length' uses a dedicated NodeWhereCount that counts elements
  matching the predicate, no allocation.
- 'where (P) any/all/none (S)' is rewritten at parse time as a quantifier
  over the source with a composed predicate, reusing the existing
  zero-allocation quantifier evaluator.

Performance is on par with native quantifiers (1 alloc from Go runtime
[]any boxing, ~110-135ns).

Limitations:
- No chained where (where (...) where (...)).
- Only .length, any, all, none may follow where.
- 'where' is a reserved keyword; contexts using a field named 'where'
  will conflict.
- Not part of nikunjy/rules; rules using where won't run on that library.
@coderabbitai

coderabbitai Bot commented May 21, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c4a7d162-7b4d-4b91-9bd3-b394eb3c5d4c

📥 Commits

Reviewing files that changed from the base of the PR and between 09b8a98 and b86a2a7.

📒 Files selected for processing (3)
  • CLAUDE.md
  • parser_test.go
  • validator.go
✅ Files skipped from review due to trivial changes (1)
  • CLAUDE.md

📝 Walkthrough

Walkthrough

This PR introduces a proprietary where (predicate) list-filtering operator to the rule engine. The operator filters array elements by a predicate, then permits counting via .length or applying quantifiers (any, all, none) to the filtered subset. Lexer, parser, validator, and evaluator support the feature end-to-end with comprehensive test fixtures, examples, and benchmarks.

Changes

Where Filter Operator

Layer / File(s) Summary
Token and AST contract
token.go, ast.go
WHERE keyword token and NodeWhereCount AST node type define the syntactic foundation for where expressions.
Error definitions for where validation
errors.go
Four sentinel errors enforce where-syntax rules: parentheses requirement, non-empty predicates, and mandatory .length or quantifier follow-up.
Parser: where expression syntax and rewriting
parser.go, parser_test.go, lexer_test.go
parseWhereExpression recognizes where (predicate) followed by .length or quantifiers; validates syntax; rewrites quantifier forms into equivalent compositions; updates operator classification and token exclusion lists.
Validator: where operation structure checks
validator.go
validateWhereCountOperation enforces non-nil left operand restricted to identifier/property, required non-nil predicate child, and proper operator whitelisting.
Evaluator: runtime where evaluation and operator dispatch
evaluator.go
evaluateWhereCount iterates source arrays and counts predicate-matching elements; returns 0 for invalid/non-array sources; operator dispatch rejects WHERE in unary/binary/comparison contexts.
Tests: parsing, fixtures, examples, and benchmarks
example_test.go, benchmark_optimized_test.go, test/list_fixtures.go, test/rule_engine_test.go, parser_test.go, lexer_test.go
Parser tests verify AST rewriting; 241-line test fixture suite covers length checks, compound predicates, quantifier interactions, and edge cases; executable examples and performance benchmarks validate correctness and near-zero allocation overhead.
User documentation and concept definitions
README.md, concept.md, CLAUDE.md
Documents syntax, examples, behavioral constraints (reserved keyword, no chaining, array-of-objects requirement), quantifier/length interactions, and differences from nikunjy/rules.

Sequence Diagram

sequenceDiagram
  participant Lexer
  participant Parser
  participant Validator
  participant Evaluator
  Lexer->>Parser: WHERE token + identifier + parens
  Parser->>Parser: parseWhereExpression validates syntax
  Parser->>Parser: rewrite quantifiers to boolean AST
  Parser->>Validator: NodeWhereCount AST
  Validator->>Validator: validateWhereCountOperation checks structure
  Validator->>Evaluator: validated AST node
  Evaluator->>Evaluator: evaluateWhereCount iterates & counts
  Evaluator->>Evaluator: return integer result
Loading

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature being added: a new 'where' filter operator for arrays.
Description check ✅ Passed The description covers what the feature does, provides concrete examples, explains the implementation approach, and documents limitations. However, the required checklist items remain unchecked.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch zaqueu/where-clause

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
parser_test.go (1)

371-415: ⚡ Quick win

Add a dedicated where + none rewrite test.

parseWhereWithQuantifier has a distinct NONE rewrite branch, but this unit set only pins ANY and ALL. Add one focused NONE case to prevent regressions in rewrite semantics.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@parser_test.go` around lines 371 - 415, Add a new unit test mirroring
TestParserWhereWithAny/TestParserWhereWithAll to cover the NONE rewrite branch:
call ParseRule with a "selections where (...) none (...)" input and assert the
AST top node is NodeBinaryOp with Operator NONE (or that ParseRule triggers the
rewrite implemented in parseWhereWithQuantifier for NONE); then verify the Right
side is the expected composed expression for the NONE rewrite (e.g., an OR where
the first part is NOT predicate, or whatever parseWhereWithQuantifier defines
for NONE). Use the same assertion style as
TestParserWhereWithAny/TestParserWhereWithAll and name the test
TestParserWhereWithNone to prevent regressions in parseWhereWithQuantifier's
NONE handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CLAUDE.md`:
- Line 131: The sentence "Encadeamento de `where` não é suportado. Acesso
pós-`where` é restrito a `.length`." is in Portuguese; replace it with an
English equivalent so the doc is consistent—e.g., "Chaining of `where` is not
supported. Post-`where` access is restricted to `.length`." Ensure you update
the line that mentions `where` and `.length` accordingly.

In `@validator.go`:
- Around line 39-40: The NodeWhereCount branch returns after basic checks and
does not recurse into the WHERE subtree, so predicate/source expressions escape
semantic validation; update the NodeWhereCount handling in
validateWhereCountOperation (and the similar block around lines 150-167) to
recursively validate the where subtree by invoking the existing recursive
validator(s) (e.g., call validateNode or reuse
validateWhereOperation/validateWherePredicate on the node's Source and Predicate
children) so ParseRule checks run on the predicate/source expressions and any
errors are reported.

---

Nitpick comments:
In `@parser_test.go`:
- Around line 371-415: Add a new unit test mirroring
TestParserWhereWithAny/TestParserWhereWithAll to cover the NONE rewrite branch:
call ParseRule with a "selections where (...) none (...)" input and assert the
AST top node is NodeBinaryOp with Operator NONE (or that ParseRule triggers the
rewrite implemented in parseWhereWithQuantifier for NONE); then verify the Right
side is the expected composed expression for the NONE rewrite (e.g., an OR where
the first part is NOT predicate, or whatever parseWhereWithQuantifier defines
for NONE). Use the same assertion style as
TestParserWhereWithAny/TestParserWhereWithAll and name the test
TestParserWhereWithNone to prevent regressions in parseWhereWithQuantifier's
NONE handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cebff0fb-48a6-4b31-a0f5-b835f18e08ff

📥 Commits

Reviewing files that changed from the base of the PR and between 5f5d2f6 and 09b8a98.

📒 Files selected for processing (15)
  • CLAUDE.md
  • README.md
  • ast.go
  • benchmark_optimized_test.go
  • concept.md
  • errors.go
  • evaluator.go
  • example_test.go
  • lexer_test.go
  • parser.go
  • parser_test.go
  • test/list_fixtures.go
  • test/rule_engine_test.go
  • token.go
  • validator.go

Comment thread CLAUDE.md Outdated
Comment thread validator.go Outdated
- Translate Portuguese text in CLAUDE.md to English (where chaining/access notes)
- Recursively validate NodeWhereCount source and predicate subtrees so
  semantic errors inside the where predicate are caught at parse time.
  Extracted into validateWhereCountNode to keep ValidateAST cognitive
  complexity in check.
- Add TestParserWhereWithNone covering the NONE rewrite branch in
  parseWhereWithQuantifier (where (P) none (S) -> source none (P and S))
@mzaqueu
mzaqueu merged commit d0b44bf into main May 22, 2026
2 checks passed
@mzaqueu
mzaqueu deleted the zaqueu/where-clause branch May 22, 2026 15:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants