Skip to content

Refactor index system to use EntityFilter instead of TableIndices - #1306

Merged
DZakh merged 4 commits into
mainfrom
claude/nice-einstein-xphi3i
Jun 11, 2026
Merged

Refactor index system to use EntityFilter instead of TableIndices#1306
DZakh merged 4 commits into
mainfrom
claude/nice-einstein-xphi3i

Conversation

@DZakh

@DZakh DZakh commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

Consolidates the index/filter abstraction by moving FieldValue and filter matching logic from TableIndices into EntityFilter, and refactoring the in-memory index system to work directly with EntityFilter.t instead of intermediate TableIndices.Index.t objects.

Key Changes

  • Moved FieldValue module from TableIndices to EntityFilter with all comparison operators (eq, gt, lt)
  • Removed TableIndices module entirely (was only used for index representation)
  • Added filter matching to EntityFilter:
    • toString: Generates stable cache keys for filters
    • matches: Evaluates whether an entity matches a filter (replaces TableIndices.Index.evaluate)
  • Refactored InMemoryTable.Entity:
    • Renamed indicesByEntityIdfiltersByEntityId and fieldNameIndicesfilterIndices
    • Changed from nested dict structure (fieldName → serialized index → index data) to flat dict (filter key → filter data)
    • Simplified updateIndices and deleteEntityFromIndices to work directly with EntityFilter.t
    • Removed makeIndicesSerializedToValue helper
  • Updated LoadLayer.loadByFieldLoadLayer.loadByFilter:
    • Now accepts EntityFilter.t directly instead of separate fieldName, operator, fieldValue parameters
    • Simplified index creation and lookup logic
  • Updated UserContext.getWhereHandler:
    • Constructs EntityFilter.t values directly instead of using TableIndices.Operator.t
    • Extracted common loadWithFilter helper to reduce duplication
  • Updated TestIndexer.handleLoad:
    • Replaced custom matcher functions with EntityFilter.matches
    • Simplified filter parsing to reconstruct EntityFilter.t with parsed field values

Implementation Details

  • Filter keys are generated via EntityFilter.toString, providing unambiguous cache keys for any filter configuration
  • Entity field values are cast to EntityFilter.FieldValue.t (an option type) to handle missing/nullable fields gracefully
  • The And filter recursively evaluates all nested filters, matching storage layer semantics
  • All field value comparisons (including BigDecimal) are centralized in FieldValue module for consistency

https://claude.ai/code/session_01QN8cfbDEtGeoSGRxSXirpt

Summary by CodeRabbit

  • Refactor
    • Restructured internal query indexing system to use unified filter-based indexing, improving query consistency and performance for entity filtering operations.

Move FieldValue comparison logic into EntityFilter and add toString
(stable in-memory cache key) and matches (evaluates a filter against an
entity) so the single-field TableIndices module is no longer needed.

- InMemoryTable keys its in-memory indices by EntityFilter.toString
  with a single flat dict instead of the per-field two-level structure
- LoadLayer.loadByField becomes loadByFilter and passes the filter
  straight to storage.loadOrThrow
- UserContext builds EntityFilter leaves directly; _in still decomposes
  into per-value Eq loads and _gte/_lte into Eq + Gt/Lt to keep
  per-value memoization
- TestIndexer reuses EntityFilter.matches after parsing leaf values
  with the field schemas

https://claude.ai/code/session_01QN8cfbDEtGeoSGRxSXirpt
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@DZakh, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 15 minutes and 33 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 473a683e-3545-4a96-9715-cd9a55a40d4f

📥 Commits

Reviewing files that changed from the base of the PR and between 314be25 and 4f21cf4.

📒 Files selected for processing (1)
  • packages/envio/src/db/EntityFilter.res
📝 Walkthrough

Walkthrough

This PR refactors entity loading and indexing from field/operator-based index hashing to EntityFilter-based evaluation. EntityFilter gains FieldValue comparison and filter stringification; InMemoryTable switches from field/operator indices to filter tracking; LoadLayer migrates to filter-driven batch loading; UserContext and TestIndexer adopt the new filter-based APIs; and the LoadLayer test suite is updated accordingly.

Changes

Filter-based Indexing Refactor

Layer / File(s) Summary
EntityFilter FieldValue module and filter evaluation
packages/envio/src/db/EntityFilter.res
EntityFilter.FieldValue submodule adds typed value comparison (eq, gt, lt) with BigDecimal-specific logic; EntityFilter gains toString for stable filter cache keys and matches to evaluate filter predicates against entity dictionaries.
InMemoryTable indexing migration to filter-based strategy
packages/envio/src/InMemoryTable.res
InMemoryTable record replaces indicesByEntityId/fieldNameIndices with filtersByEntityId/filterIndices; initialization and drop logic clear filter indices; updateIndices/deleteEntityFromIndices rewritten to use EntityFilter.matches and incrementally track entity membership in filters.
LoadLayer loadByFilter API and storage integration
packages/envio/src/LoadLayer.res, packages/envio/src/LoadLayer.resi
LoadLayer.loadByFilter replaces loadByField: constructs LoadManager keys from EntityFilter variants, batches filters through storage.loadOrThrow(~filter), registers empty in-memory indexes per filter, and initializes entities; observability and LoadManager.call wiring use EntityFilter.toString for hashing.
UserContext and TestIndexer filter-based refactoring
packages/envio/src/UserContext.res, packages/envio/src/TestIndexer.res
UserContext.getWhereHandler translates operator keys into EntityFilter objects and loads via loadWithFilter; TestIndexer.handleLoad parses filter values through entity field schemas and validates via EntityFilter.matches.
Test suite LoadLayer API updates
scenarios/test_codegen/test/LoadLayer_test.res
LoadLayer_test.res updates all loadByField invocations to loadByFilter with constructed EntityFilter.Eq/Gt/Lt objects, maintaining assertion semantics across load non-existing, index existence, and post-index-creation entity scenarios.

Possibly related PRs

  • enviodev/hyperindex#1303: Both PRs pivot the load path from field/operator-based lookups to EntityFilter predicates—this PR's LoadLayer.loadByFilter and filter-derived indexing changes directly follow that PR's storage.loadOrThrow(~filter, ~table) unification.
  • enviodev/hyperindex#1269: Both PRs modify InMemoryTable.res's committed-change reset/drop path; this PR switches it from clearing indicesByEntityId/fieldNameIndices to clearing new filterIndices.
  • enviodev/hyperindex#1275: Both PRs modify InMemoryTable.res around checkpoint/state reset and index bookkeeping; this PR rewires the same reset/drop/index-update logic to use filterIndices/filtersByEntityId instead of indicesByEntityId/fieldNameIndices.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main architectural change: replacing the TableIndices-based index system with EntityFilter-based indexing across multiple modules.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.


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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/envio/src/db/EntityFilter.res (1)

41-53: 💤 Low value

Verify behavior of gt/lt for mismatched or None values.

The fallback a > b and a < b comparisons on option types rely on OCaml's polymorphic comparison. When comparing None with Some(_) or values of different underlying types (e.g., Some(Int(1)) vs Some(String("x"))), the result may be unintuitive or non-deterministic across JS engines. If such comparisons are expected at runtime, consider explicit handling.

🤖 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 `@packages/envio/src/db/EntityFilter.res` around lines 41 - 53, The gt/lt
functions currently fall back to polymorphic comparisons (a > b / a < b) which
can yield unintuitive results for None vs Some or mismatched Some types; update
gt and lt to explicitly handle option shapes: add cases for (None, None), (None,
Some(_)) and (Some(_), None) and a clear policy for mismatched Some(x)/Some(y)
(e.g., return false, raise an error, or coerce/compare only when types match),
keeping the existing BigDecimal branch for (Some(BigDecimal...),
Some(BigDecimal...)); reference the gt and lt functions and the BigDecimal
pattern to locate where to add these explicit patterns.
🤖 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 `@packages/envio/src/db/EntityFilter.res`:
- Around line 76-77: The In branch is casting the entire array to FieldValue.t
which is incorrect; update the In({fieldName, fieldValue}) handler to iterate
over the array (fieldValue), for each element call FieldValue.castFrom(...) and
then FieldValue.toString(...) and join the results into a bracketed,
comma-separated string so the returned template uses
`${fieldName}:In:[elem1,elem2,...]`; reference the In pattern and the
FieldValue.castFrom / FieldValue.toString helpers when making this change.

---

Nitpick comments:
In `@packages/envio/src/db/EntityFilter.res`:
- Around line 41-53: The gt/lt functions currently fall back to polymorphic
comparisons (a > b / a < b) which can yield unintuitive results for None vs Some
or mismatched Some types; update gt and lt to explicitly handle option shapes:
add cases for (None, None), (None, Some(_)) and (Some(_), None) and a clear
policy for mismatched Some(x)/Some(y) (e.g., return false, raise an error, or
coerce/compare only when types match), keeping the existing BigDecimal branch
for (Some(BigDecimal...), Some(BigDecimal...)); reference the gt and lt
functions and the BigDecimal pattern to locate where to add these explicit
patterns.
🪄 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: 0b6468eb-8847-46c7-8501-11eb44f3563f

📥 Commits

Reviewing files that changed from the base of the PR and between 71ff7d8 and 314be25.

📒 Files selected for processing (8)
  • packages/envio/src/InMemoryTable.res
  • packages/envio/src/LoadLayer.res
  • packages/envio/src/LoadLayer.resi
  • packages/envio/src/TableIndices.res
  • packages/envio/src/TestIndexer.res
  • packages/envio/src/UserContext.res
  • packages/envio/src/db/EntityFilter.res
  • scenarios/test_codegen/test/LoadLayer_test.res
💤 Files with no reviewable changes (1)
  • packages/envio/src/TableIndices.res

Comment thread packages/envio/src/db/EntityFilter.res Outdated
@DZakh
DZakh enabled auto-merge (squash) June 11, 2026 10:35
@DZakh
DZakh merged commit 93c992a into main Jun 11, 2026
8 checks passed
@DZakh
DZakh deleted the claude/nice-einstein-xphi3i branch June 11, 2026 10:39
DZakh pushed a commit that referenced this pull request Jun 11, 2026
getWhere now builds EntityFilter values directly (main's #1306); the
filters carry API field names, which PgStorage resolves to the possibly
renamed Postgres columns via Table.queryFields.

https://claude.ai/code/session_01NDYWTmD2zFCoE7fqQqS7QK
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