Skip to content

feat: secondary indexes on scalar properties (indexed=True) - #12

Merged
deucalioncodes merged 1 commit into
mainfrom
feat/indexed-fields
Jul 18, 2026
Merged

deucalioncodes merged 1 commit into
mainfrom
feat/indexed-fields

Conversation

@deucalioncodes

Copy link
Copy Markdown
Member

Summary

  • Adds declarative secondary indexes: String(..., indexed=True) (and Integer/Float/Boolean) maintains a persistent per-value ID list under _fi:{Type}:{field}:{value} keys — the same storage pattern as the existing relation reverse indexes.
  • New Entity classmethods:
    • find_by(field, value, from_id=1, count=50) — equality lookup with cursor pagination, cost proportional to matches instead of max_id
    • count_by(field, value) — match count without loading entities
    • rebuild_field_index(field, from_id=1, batch=200) — resumable backfill for entities that predate the index, bounded per call for IC instruction-limit safety
  • Entity.delete() cleans up index entries; hydration from storage skips hooks/validation/index writes in Property.__set__ (mirrors the Relation fast path).
  • The indexed flag is intentionally excluded from the schema descriptor, so toggling it never triggers upgrade-compatibility errors.

Closes #11

Test plan

  • New tests/src/tests/test_indexed_fields.py (15 cases): lookups, non-string values, update/None/delete maintenance, persistence across load, pagination cursor semantics, resumable + idempotent rebuild, unindexed-field errors
  • Wired into run_test.sh, entrypoint.sh (IC), and the test canister main.py
  • Full local suite passes (all 16 modules), run_linters.sh clean (black, isort, flake8, mypy)
  • CI: Test / Test IC workflows

Made with Cursor

Declaring indexed=True on String/Integer/Float/Boolean maintains a
persistent per-value ID list (key format _fi:{Type}:{field}:{value},
same pattern as relation reverse indexes), kept up to date by the
property descriptor on create/update/delete. New Entity classmethods:

- find_by(field, value, from_id, count): equality lookup with cursor
  pagination, cost proportional to matches instead of max_id
- count_by(field, value): match count without loading entities
- rebuild_field_index(field, from_id, batch): resumable backfill for
  entities that predate the index, bounded per call so IC canisters can
  spread it across update calls without hitting instruction limits

Entity hydration from storage now skips hooks, validation, and index
maintenance in Property.__set__ (indexes are already intact), mirroring
the existing Relation fast path.

The indexed flag is intentionally excluded from the schema descriptor:
toggling it is an operational change, not a data-shape change.

Closes #11

Co-authored-by: Cursor <cursoragent@cursor.com>
@deucalioncodes
deucalioncodes merged commit 01bec51 into main Jul 18, 2026
5 checks passed
@deucalioncodes
deucalioncodes deleted the feat/indexed-fields branch July 18, 2026 19:59
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.

Secondary indexes on entity fields (indexed=True)

1 participant