Skip to content

feat(platform)!: integerRange index buckets an integer property into windows - #5170

Merged
QuantumExplorer merged 4 commits into
v4.2-devfrom
claude/integer-groupings-93dcc1
Sep 29, 2026
Merged

QuantumExplorer merged 4 commits into
v4.2-devfrom
claude/integer-groupings-93dcc1

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Basic explanation

What this does: A contract can already group an index by time windows (timeRange: "posts per hour"). This adds the same thing for whole numbers: integerRange groups an index by windows of an integer property, such as a price or a score, so "listings priced 600 to 899, per category" or "players per score band of 100" is one lookup instead of a scan. A query picks one window by where it starts, and counts, sums and rankings on the index then apply to that window, with proofs.

Value: Contracts get banded counts, totals and leaderboards over any integer property with the same proof guarantees as time windows, and unique indexes can express "one per band" (for example one bid per owner per price band of 1000).

Risks: Medium-low. It is consensus code (a new index keyword, new walker branches, a new query operator), but it is all new at protocol version 14, which is not yet on mainnet: no stored contract can carry integerRange, and every shared code path takes its old branch when an index has none. The write walkers now read a shared "bucketing" type instead of the time grid directly; the existing time-range e2e suite passes unchanged. Because the grid rules are now shared, some timeRange refusal messages are reworded (and a contract breaking several rules may see a different one first); that is also protocol version 14 only. Two calls for the reviewer: indexOnly types refuse integerRange (see below), and integer windows reuse the time grid's overlap cap (max_time_range_overlap_factor, 24) rather than a separate limit. The Swift and Kotlin example apps do not display the new keyword yet.

Issue being fixed or feature implemented

Time windows (timeRange) only bucket system timestamps. There was no way to group documents by bands of an integer property, so per-band counts or rankings needed one index entry per exact value and a scan over them.

What was done?

Contract grammar (document meta-schema v3, protocol version 14):

"indices": [{
  "name": "byPriceBand",
  "properties": [{ "price": "asc" }, { "category": "asc" }],
  "integerRange": { "on": "price", "range": 300, "step": 100 },
  "countable": "countable"
}]
  • on is the index's first property: a required integer property of at most 64 bits (the width and sign come from its minimum/maximum, as for any integer property). A nested source must sit inside required objects, and the grid-qualified level key must fit GroveDB's 255 bytes.
  • Windows start at phase + k * step for every integer k (negative included) and cover [start, start + range). range is a multiple of step, range / step is capped by the existing overlap cap (24), phase < step. No ttl.
  • A start below the lowest value of the property's type is clamped to that value, so every value is in at least one window. On an unsigned property with no phase this only drops windows below 0.
  • The stored key is the window start, encoded like a value of the property, under a level keyed price#300#100 (the time-range level key format).
  • Unique only with range == step. The source may change on update: the uniqueness probe (v1) looks in the candidate's window and lets a document change its value within its own window.
  • The index's other properties can be skipIfAbsent properties (the bucketed one is required, so it never is): a document missing one writes no entry in any window, and an update that adds or drops it moves the document into or out of all of its windows.
  • Refused with contested, nullSearchable: false, preallocated, timeRange, a ranking at the bucketed level, as a refersTo lookup target, as a propertyConstraints answering index, and on indexOnly document types (an indexOnly entry is keyed by the index's values and terminal, so rows that differ only in the bucketed integer would collide in the windows they share).
  • The rules both grid kinds share (unique needs range == step, not contested, ranking only below the bucketed level, nullSearchable, step/phase/range shape, first property, the overlap cap) now run through one helper for timeRange and integerRange.

Before / after, per window count:

Before, with a plain (price, category) index, a "books between 600 and 899" count is a range count over every distinct price. After, with the index above:

sdk.documents.count({
  dataContractId, documentTypeName: "listing",
  where: [["category", "==", "books"]],
  integerRange: [{ field: "price", start: 600 }],
});

reads one window key and returns a proved count.

Query surface:

  • platform.proto: WhereOperator.IN_INTEGER_RANGE = 12 with a typed IntegerRangeSelection { start, grid } operand in WhereClause.integer_range.
  • Drive: resolve_integer_range_bucket_clause turns a selection into a window-start equality, from the query alone (no clock). The resolution provenance (ResolvedTimeRange) now carries an IndexBucketing (time or integer grid), so every index picker keeps raw queries off both kinds of bucketed index through the one admissibility rule.
  • drive-abci v1 handler resolves the operator next to IN_TIME_RANGE; chained and composite requests refuse it as they refuse time selections.
  • SDK (dash-platform-queries): DocumentQuery::with_integer_range / with_integer_range_grid, encoded on the v1 wire and resolved identically in every proof verifier.
  • wasm-sdk: an integerRange: [{ field, start, grid? }] member on document, count/sum/average, ranked and having queries (start may be a decimal string above Number.MAX_SAFE_INTEGER). documentTypeLayout describes integer windows (integerRangeBucket key, integerRangeOverlap note).

Shared code:

  • IndexBucketing (dpp) is the one view of a time or integer grid: source, level key, overlap factor and the entry-key fan-out. IndexLevel stores it; the insert (v2), delete (v2) and update (v1) walkers, the indexOnly paths and cost estimation read it. TTL handling still reads the time grid only.
  • A document ttl prices every window an integer-range index writes, and documentCreateCost samples an integer-range source clear of the clamped bottom window so it prices every window.

Docs and generated files: new book chapter contract-keywords/integer-range.md, keyword table rows, cross-references, meta-schema descriptions, and note 60 in v14.rs (this also renumbers the skipIfAbsent note, which shared number 58 with the $moderatedAt note on the base branch, to 59). The GroveDB structure description mentions integer windows (regenerated grovedb-structure.json), and the dapi-grpc clients are regenerated for the new operator.

In-place changes to shipped generations

None of these can change behaviour for a contract that exists today: integerRange parses only in the generation-3 grammar (protocol version 14, not yet on mainnet), so Index::integer_range is None on every stored contract.

  • Document meta-schema v3, try_from_schema generation 3, Index parsing: new keyword, admitted only by generation 3.
  • validate_uniqueness_of_data v1 (selected by protocol versions 10 to 13, and by 14 through the v2 wrapper): the integer-window probe and the exemption of a changed integer-range source from allow_original = false only fire when index.integer_range is set, which no contract before 14 can have. The edited lines say so; the existing PV13 tests validate_uniqueness_of_data_v1_* pin the unchanged outcome.
  • single_in_path_query v0 (every protocol version): the cursor check reads Index::bucketing() instead of time_range; both are None for every contract before 14. The edited lines say so.
  • Insert walker v2, delete walker v2, update walker v1: protocol version 14 only.
  • propertyConstraints answering index: !is_bucketed() equals the old time_range.is_none() without integer grids.
  • Document expiry pricing: counts integer windows only when an index has an integer grid.
  • The contract-update message for a changed time grid is byte-identical.

How Has This Been Tested?

  • dpp: bucket math (overlap, phase, negative values, the clamped bottom window, u64/i64 extremes, key order), grammar rules, document-type rules (key type resolved on validated and unvalidated parses, non-integer/system/optional sources refused, a nested source under an optional object refused, the level key cap, overlap cap, pre-v14 refusal, indexOnly refusal), and a width change of the source reported as the property's integer-encoding change.
  • Drive e2e: fan-out on insert and removal on delete, moving between windows on update (non-unique and unique), the clamped signed bottom window, off-grid and ambiguous-grid refusals, a raw equality kept off the bucketed index, a proved window query; uniqueness per window including a value change inside the document's own window (mutation-checked: removing the exemption fails the test); the create cost prices every window and matches what Drive charges; a document entering and leaving every window of a skipIfAbsent integer-range index as an update adds and drops the skipped property (mutation-checked: letting the new side ignore the skip leaves a null-keyed entry and fails the test).
  • drive-abci: a count and a documents query over an overlapping window proved by the v1 handler and verified through the SDK FromProof entry points (each document counted once), a proof of one window refused for another, and malformed selections refused.
  • Full dpp and drive lib suites, drive-abci query::document_query, dash-platform-queries (including a composite page refusing a window selection before sending), wasm-sdk lib tests, dapi-grpc JS tests; clippy on the touched crates.

Breaking Changes

Consensus: a new index keyword and query operator at protocol version 14 (unreleased).

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed
  • If I added or changed GroveDB structure, I described it in the area's structure.rs, regenerated grovedb-structure.json, and checked the structure viewer link posted on this pull request (described and regenerated; the viewer link is not checked yet)

🤖 Generated with Claude Code

PR Hygiene · 6de8c9f

  • Bots — coderabbitai not yet · thepastaclaw not yet — /skip-bots proceeds without the ones not yet reported
  • Self-review — post /self-reviewed once the bots are done
  • Within your 5 open PRs — this one is beyond the limit; it waits until one merges
  • Build running
  • Approvals
    • files with no dedicated owner — you own it
    • dpp — you own it
    • rs-drive-abci — you own it
    • rs-drive — you own it
    • rs-platform-wallet (packages/rs-platform-wallet/src/wallet/identity/network/contact_info.rs, packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs, packages/rs-platform-wallet/src/wallet/identity/network/profile.rs) — HashEngineering or ZocoLini or llbartekll or romchornyi
    • rust-sdk (packages/rs-sdk/src/platform/dashpay/contact_request_queries.rs, packages/rs-sdk/src/platform/dpns_usernames/mod.rs, packages/rs-sdk/src/platform/dpns_usernames/queries.rs and 1 more) — lklimek or shumkov
    • js-wasm-sdk (packages/wasm-sdk/src/document_type_layout.rs, packages/wasm-sdk/src/dpns.rs, packages/wasm-sdk/src/queries/chained_document.rs and 3 more) — shumkov

When every box is checked the PR Hygiene check passes and this can merge.

QuantumExplorer and others added 2 commits September 29, 2026 07:42
…to windows

Adds the integerRange index keyword to the v3 document meta-schema
(protocol version 14): { on, range, step, phase } buckets the index's
first property, a required integer property of at most 64 bits, into
windows starting at phase + k * step. A start below the lowest value the
property's integer type holds is clamped to it, so every value lands in
at least one window. The parsed grid carries the source's key encoding,
resolved from the schema on every parse.

IndexBucketing is the shared view of a timeRange or integerRange grid;
IndexLevel now stores it. indexOnly proof indexes, refersTo lookups and
propertyConstraints answering indexes exclude integer-bucketed indexes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Carries the dpp integerRange keyword through the stack:

- Drive walkers (insert v2, delete v2, update v1), indexOnly paths, cost
  estimation and the storage layout read one IndexBucketing view; the
  uniqueness probe (v1) looks in the candidate's window and lets a
  document change its value within its own window.
- IN_INTEGER_RANGE = 12 with a typed IntegerRangeSelection operand on the
  v1 getDocuments wire, resolved to a window-start equality from the query
  alone on the server and in every SDK proof verifier; the resolution
  provenance now carries either kind of grid.
- DocumentQuery::with_integer_range[_grid] in dash-platform-queries and an
  integerRange member on the wasm-sdk document, aggregate, ranked and
  having queries; documentTypeLayout describes integer windows.

Review fixes: the grid rules, overlap cap and grid selection shared by
both kinds; a nested source must sit in required objects; the level key
must fit 255 bytes; indexOnly types refuse integerRange (rows differing
only in the bucketed integer would collide per window); a width change of
the source is reported as the property's integer-encoding change;
documentCreateCost prices every window; the composite and chained SDK
guards refuse integer selections; refusals name the right operator.
Regenerated dapi-grpc clients and grovedb-structure.json; book chapter
contract-keywords/integer-range.md and v14 note 57.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 2 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: dashpay/platform/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: b3365f77-9199-410c-ad17-651d733e7e76

📥 Commits

Reviewing files that changed from the base of the PR and between 7f513f5 and 6de8c9f.

📒 Files selected for processing (92)
  • book/src/SUMMARY.md
  • book/src/contract-keywords.md
  • book/src/contract-keywords/contested.md
  • book/src/contract-keywords/index-only.md
  • book/src/contract-keywords/indexes.md
  • book/src/contract-keywords/integer-range.md
  • book/src/contract-keywords/property-constraints.md
  • book/src/contract-keywords/ranked.md
  • book/src/contract-keywords/refers-to-lookup.md
  • book/src/contract-keywords/time-range.md
  • book/src/data-model/documents.md
  • book/src/drive/index-only-document-types.md
  • packages/dapi-grpc/build.rs
  • packages/dapi-grpc/clients/drive/v0/nodejs/drive_pbjs.js
  • packages/dapi-grpc/clients/platform/v0/nodejs/platform_pbjs.js
  • packages/dapi-grpc/clients/platform/v0/nodejs/platform_protoc.js
  • packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.h
  • packages/dapi-grpc/clients/platform/v0/objective-c/Platform.pbobjc.m
  • packages/dapi-grpc/clients/platform/v0/python/platform_pb2.py
  • packages/dapi-grpc/clients/platform/v0/web/platform_pb.d.ts
  • packages/dapi-grpc/clients/platform/v0/web/platform_pb.js
  • packages/dapi-grpc/protos/platform/v0/platform.proto
  • packages/dash-platform-queries/src/documents/chained_document_query.rs
  • packages/dash-platform-queries/src/documents/composite_document_query.rs
  • packages/dash-platform-queries/src/documents/count_proof_helpers.rs
  • packages/dash-platform-queries/src/documents/document_query.rs
  • packages/rs-dpp/schema/meta_schemas/document/v3/document-meta.json
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/common/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v1/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/index_only_tests.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/class_methods/try_from_schema/v3/ranked_prefix_overlap.rs
  • packages/rs-dpp/src/data_contract/document_type/index/bucketing.rs
  • packages/rs-dpp/src/data_contract/document_type/index/integer_range.rs
  • packages/rs-dpp/src/data_contract/document_type/index/integer_range_parse_tests.rs
  • packages/rs-dpp/src/data_contract/document_type/index/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/index/preallocation.rs
  • packages/rs-dpp/src/data_contract/document_type/index/random_index.rs
  • packages/rs-dpp/src/data_contract/document_type/index_level/find_first_change.rs
  • packages/rs-dpp/src/data_contract/document_type/index_level/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/methods/validate_update/v1/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/property/reference_lookup.rs
  • packages/rs-dpp/src/data_contract/document_type/property_constraints/aggregate.rs
  • packages/rs-drive-abci/src/query/document_query/v1/conversions.rs
  • packages/rs-drive-abci/src/query/document_query/v1/dispatch/chained.rs
  • packages/rs-drive-abci/src/query/document_query/v1/dispatch/composite.rs
  • packages/rs-drive-abci/src/query/document_query/v1/mod.rs
  • packages/rs-drive-abci/src/query/document_query/v1/routing.rs
  • packages/rs-drive-abci/src/query/document_query/v1/tests.rs
  • packages/rs-drive/grovedb-structure.json
  • packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/index_only_e2e_tests.rs
  • packages/rs-drive/src/drive/contract/insert/insert_contract/v0/tests/ranked_index_e2e_tests.rs
  • packages/rs-drive/src/drive/document/cost/document.rs
  • packages/rs-drive/src/drive/document/cost/tests.rs
  • packages/rs-drive/src/drive/document/cost/writes.rs
  • packages/rs-drive/src/drive/document/delete/remove_indices_for_top_index_level_for_contract_operations/v2/mod.rs
  • packages/rs-drive/src/drive/document/expiration/pricing.rs
  • packages/rs-drive/src/drive/document/index_level_tree_types.rs
  • packages/rs-drive/src/drive/document/index_only.rs
  • packages/rs-drive/src/drive/document/index_uniqueness/internal/validate_uniqueness_of_data/v1/mod.rs
  • packages/rs-drive/src/drive/document/index_uniqueness/mod.rs
  • packages/rs-drive/src/drive/document/insert/add_document_for_contract/integer_range_index_e2e_tests.rs
  • packages/rs-drive/src/drive/document/insert/add_document_for_contract/mod.rs
  • packages/rs-drive/src/drive/document/insert/add_document_for_contract/time_range_index_e2e_tests.rs
  • packages/rs-drive/src/drive/document/insert/add_indices_for_top_index_level_for_contract_operations/v2/mod.rs
  • packages/rs-drive/src/drive/document/layout.rs
  • packages/rs-drive/src/drive/document/structure.rs
  • packages/rs-drive/src/drive/document/update/internal/update_document_for_contract_operations/v1/mod.rs
  • packages/rs-drive/src/query/drive_document_average_query/drive_dispatcher.rs
  • packages/rs-drive/src/query/drive_document_count_query/tests.rs
  • packages/rs-drive/src/query/drive_document_having_query/drive_dispatcher.rs
  • packages/rs-drive/src/query/drive_document_ranked_query/drive_dispatcher.rs
  • packages/rs-drive/src/query/drive_document_ranked_query/tests.rs
  • packages/rs-drive/src/query/drive_document_sum_query/tests.rs
  • packages/rs-drive/src/query/index_only_synthesis.rs
  • packages/rs-drive/src/query/mod.rs
  • packages/rs-drive/src/query/non_primary_key_path_query/single_in_path_query/v0/mod.rs
  • packages/rs-platform-version/src/version/v14.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/contact_info.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/profile.rs
  • packages/rs-sdk/src/platform/dashpay/contact_request_queries.rs
  • packages/rs-sdk/src/platform/dpns_usernames/mod.rs
  • packages/rs-sdk/src/platform/dpns_usernames/queries.rs
  • packages/rs-sdk/src/platform/query.rs
  • packages/wasm-sdk/src/document_type_layout.rs
  • packages/wasm-sdk/src/dpns.rs
  • packages/wasm-sdk/src/queries/chained_document.rs
  • packages/wasm-sdk/src/queries/composite_document.rs
  • packages/wasm-sdk/src/queries/document.rs
  • packages/wasm-sdk/src/queries/document_ranked.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions github-actions Bot added the waiting-bots Waiting for the review bots to report on this head label Sep 29, 2026
@github-actions github-actions Bot added this to the v4.2.0 milestone Sep 29, 2026
@thepastaclaw

thepastaclaw commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

🕓 Queued for automated review — 3rd in line, estimated start in ~20 min (commit 6de8c9f)
Estimated review time once started: ~50 min (two-phase automated review; median of recent runs).

  • Request priority review — click to move this review to the front of the queue.

…oupings-93dcc1

Resolves contested.md (integerRange mention next to moderatorAbilities.delete)
and v14.rs (#5158 keeps note 57; integer-range indexes become note 58).
Generated clients regenerate identically on the merged proto.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

🌳 GroveDB structure

This pull request changes the described GroveDB structure. Open it in the structure viewer: new nodes glow, removed ones stay as ghosts, and the tour walks through each change.

Changed (2 nodes)

  • contracts.contract.documents.document_type.index_property
  • contracts.contract.documents.document_type.index_property.value

Compared 7f513f58be with 6de8c9fbc9. Updated at 2026-09-29T05:05:27.309Z

@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

📖 Book Preview built successfully.

Download the preview from the workflow artifacts.
To view locally: download the artifact, unzip, and open index.html.

Updated at 2026-09-29T05:05:38.732Z

…oupings-93dcc1

Brings in $moderatedAt/$moderatedBy (#5161) and skipIfAbsent at any
position (#5162).

Conflicts resolved:
- try_from_schema common: resolve the integerRange key type and the
  skipIfAbsent set on every parse.
- insert/delete walkers v2: import both bucket_index_keys and
  level_reaches_entry.
- update walker v1: the bucketed set diff keeps skipIfAbsent's empty
  side for a version the index skips, for time and integer grids.
- layout: skip notes read the bucketing source.
- indexOnly synthesis: upstream's index_admissible_for_query already
  keeps bucketed indexes off the terminal route.
- v14 notes: skipIfAbsent 59 (it shared 58 with $moderatedAt),
  integer-range indexes 60.

Adds an e2e test of a listing moving into and out of every window of a
skipIfAbsent integer-range index, and book text for the combination.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reviewed

@QuantumExplorer
QuantumExplorer merged commit e159f38 into v4.2-dev Sep 29, 2026
30 of 31 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/integer-groupings-93dcc1 branch September 29, 2026 05:17
QuantumExplorer added a commit that referenced this pull request Sep 29, 2026
Conflict: v14.rs changelog. #5161 took item 58 (so skipIfAbsent is now
59) and #5170 / #5041 took 60 and 61; the null-flags item becomes 62.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
romchornyi pushed a commit that referenced this pull request Oct 2, 2026
v5.0-dev's DocumentQuery gained integer_range_clauses (#5170); the two
literals this branch adds leave it empty, as every other DPNS query does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-bots Waiting for the review bots to report on this head

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants