Skip to main content

Rust Order Queries

Choose a query strategy for the reader's task: export the complete matching identity set in sequence, jump between pages of a retained ID set, or calculate counts and money for the whole filtered set. Work through the Rust first Order tutorial for setup and typed Q/E reads; use the mutation recipes when changing an Order.

The retained cursor-observation/pagination workspace is WORKSPACE PASS — RELEASE PENDING. Keep the recorded Cargo workspace/config and source identity. Nullable/pagination Assist repairs remain pending generator deployment.

For the selection tradeoffs, read keyset vs offset pagination, exact counts and random access before choosing an optimizer.

Prepare the right data and context​

All three recipes use the evaluated Order v1.0 model, the complete native application and the retained built workspace/toolchain recorded in the 17-case core receipt. They are not a clean-machine bootstrap. The file includes startup, imports, store instrumentation, row conversion, numeric helpers and reporting. These are application helpers; keep them when adapting the excerpts, which are complete functions or fragments within that application. The export excerpt is a branch in the console's main match, with context already bound.

RecipeFixture and fresh databaseWhat matches
Consecutive exportNine Orders / 27 itemsEight BUYER-EXPORT Orders; one buyer control. All are USD/DRAFT. The export request filters buyer, not currency.
Retained IDs and summariesTen Orders / 30 itemsEight export-buyer USD Orders, one buyer control and one export-buyer EUR control; three statuses are represented.

The introductory three-order edit fixture has different business keys and totals. Do not seed a new fixture into an already seeded database or expect nine-fixture counts from the ten-fixture export query. Keep the absolute fixture/database path and model revision visible while diagnosing a count mismatch.

Reader taskNative strategyLifetime
Export consecutive pagesID ordering plus continuous-page optimization.Reuse one trusted context and sequence namespace inside the export.
Jump backward/forwardTotal/ID ordering plus retained-ID optimization.Reuse the context and installed store through that jump sequence.
Summary cards / status bucketsProvider COUNT/SUM/GROUP BY plus Facets.Apply the identical buyer, currency and minimum-total predicate to every request.

A namespace is not tenant authorization. These v1.0 buyer filters are business predicates; protected access needs the separate trusted isolation model/policy. The native examples seed via generated audited writes, then their query handlers leave Orders/items, versions, relations and timestamps unchanged.

1. Export consecutive pages​

Use a single-ID sort for the seekable path, page size 3, namespace order-export- plus the mode, and the example's explicit 300-second TTL. The optimization selects a provider plan; it does not serialize CSV or create a file. This console returns bounded JSON page reports. In a larger application, choose the output sink and page budget separately from the query optimization.

"pages-export"=>{
let mode=std::env::var("ORDER_EXPORT_MODE")?;let complete=mode=="asc"||mode=="desc";
assert!(["asc","desc","miss","composite","predicate"].contains(&mode.as_str()));
let mut pages=Vec::new();let(mut fallback,mut seek)=(0,0);let mut ended=false;println!("ORDER_PAGES_BEGIN");
for index in 0..(if complete {10} else {2}) {
let offset=(index+if mode=="miss" {1} else {0})*3;let buyer=if mode=="predicate"&&index==1 {"BUYER-CONTROL"} else {"BUYER-EXPORT"};
let query=Q::customer_orders().with_buyer_reference_is(buyer);
let query=if mode=="composite" {query.order_by_total_minor_asc().order_by_id_asc()} else if mode=="desc" {query.order_by_id_desc()} else {query.order_by_id_asc()};
println!("ORDER_EXPORT_PAGE {index}");
let page=query.offset(offset,3).optimize_for_continuous_page_fetch_with(format!("order-export-{mode}"),300).count()
.comment("Export Order pages").purpose("Export complete bounded Order identities through one trusted context").execute_for_list(&context).await?;
let plan=context.continuous_page_plan().ok_or("Missing native cursor observation")?;
if plan=="CURSOR_SEEK" {seek+=1} else if plan.starts_with("OFFSET_FALLBACK:") {fallback+=1}
pages.push(json!({"offset":offset,"buyer":buyer,"rows":page_rows(&page.data,false),"total":page.total_count.ok_or("Missing exact total")?,"plan":plan}));
if complete&&page.data.is_empty() {ended=true;break;}
}
assert!(!complete||ended,"Export page budget exhausted");report(&phase,json!({"mode":mode,"pages":pages,"fallbackCount":fallback,"seekCount":seek}));
},

For complete asc/desc export, the same context is reused inside the bounded loop. It stops on an empty page; ten pages is this fixture's upper safety budget, and exhausting it without an empty tail fails rather than silently returning a truncated export. The other modes run two diagnostic pages deliberately. The report keeps all pages for the small fixture; it is not an unbounded-memory export design.

Read the plan and the exact total​

Offset / ascending sequenceRow countExact matching totalNative plan
038OFFSET_FALLBACK:FIRST_PAGE
338CURSOR_SEEK
628CURSOR_SEEK
908OFFSET_FALLBACK:CACHE_MISS

The total remains 8, including the empty tail. The cursor boundary narrows the row fetch, not the matching-set count. The retained evidence contains an actual provider COUNT on every exported page; count() is not a free cached total promise. A tail cache miss is the recorded fallback behavior, not a failed export. Read this language's observation immediately after the request: context.continuous_page_plan(). The SmartList exact-count carrier is page.total_count.

ORDER_EXPORT_MODEHow to interpret it
asc / descAll eight matching identities once, in forward/reverse ID order; 3, 3, 2, 0 rows.
missStart a new sequence at offset 3: CACHE_MISS, then CURSOR_SEEK at 6.
compositeTotal then ID ordering: retain the native offset-fallback explanation instead of claiming an ID seek.
predicateChange to the control buyer at offset 3 in the same context: CACHE_MISS, zero rows, exact total 1.

The recorded Rust composite-sort plans are: OFFSET_FALLBACK:ORDER_NOT_SEEKABLE_ID on both composite-sort pages. Starting another process resets this in-memory sequence. The retained restart control starts at the first page again; the old namespace alone does not restore a process-local cursor. Fixed-data completeness is established here; the recipe does not establish a snapshot-consistent export during concurrent writes or test continuous-cursor expiry just because a TTL parameter is present.

Run the existing export​

From the site root with the retained built workspace present:

export ORDER_WORKSPACE="$PWD/artifacts/order/2026-10-27/rust/rust-app-console"
export ORDER_FIXTURE="$PWD/static/examples/order/p2-pages/fixture.json"
export ORDER_INSIGHTS_FIXTURE="$PWD/static/examples/order/p2-insights/fixture.json"
export ORDER_DATABASE="$(mktemp -d)/export.sqlite"
export ORDER_WORKFLOW_SERVICE_CORE_DATABASE_URL="sqlite://$ORDER_DATABASE"
cd "$ORDER_WORKSPACE"
run_order_phase() { ../target/debug/order-workflow-service-core-workspace "$1"; }
run_order_phase bootstrap
run_order_phase seed
export ORDER_EXPORT_MODE=asc
run_order_phase pages-export

Stop on a native command failure. Inspect the ORDER_CASE report for pages-export: page rows, totals and native plans all matter. Change the export mode and invoke that phase again to inspect another sequence; each process gets a new context and starts the selected sequence independently.

2. Retain IDs for random page jumps​

This sequence filters BUYER-EXPORT and USD, sorts by total then ID and uses page size 3, explicit TTL 300 seconds and capacity 20 IDs in the normal branch. Capacity limits identities per matching set, not the visible page size or number of cache keys. A build retains the complete ordered matching identity set, not just the requested page. Nonempty hits fetch rows by those retained identities.

First install or pass the existing store:

use teaql_runtime::IdSetStore;
let mode=std::env::var("ORDER_RETAINED_MODE")?;let store=std::sync::Arc::new(ObservedIds::default());context.set_id_set_store(store.clone());

The application creates an Arc<ObservedIds> wrapping the runtime InMemoryIdSetStore, then installs it with context.set_id_set_store(...). Store installation needs &mut UserContext; queries in that sequence reuse it. The IdSetStore trait is in scope for async get/put/invalidate, and last_key records the actual query key.

Then the existing loop executes this request, with its current mode, offset and buyer already selected. The mode selects TTL/capacity 300/20 normally, 1/20 for expiry or 300/4 for overflow:

let page=Q::customer_orders().with_buyer_reference_is(buyer).with_currency_code_is("USD").order_by_total_minor_asc().order_by_id_asc()
.offset(offset,3).optimize_pagination_with_id_set_config(format!("order-retained-{mode}"),if mode=="expiry"{1}else{300},if mode=="overflow"{4}else{20}).count()
.comment("Read retained Order IDs").purpose("Verify random jumps and retained-set lifecycle").execute_for_list(context).await?;

Observe the native plan/count through context.id_set_plan() and context.id_set_count(). That observation and the actual store events explain whether a set was built, hit or bypassed; the namespace string or repeated SQL text alone does not establish a cache hit.

Mode / offsetsExpected plan sequenceInterpretation
jumps: 6, 0, 3, 9, 0BUILD, HIT, HIT, HIT, HITTotal 8, including the empty offset-9 tail; the first request may start in the middle.
empty: 0, 3BUILD, HITRetain an empty matching set, total 0. An empty hit may need no row SQL; an absent IN query is not itself a missing hit.
overflow: 3, 0FALLBACK_LIMIT_EXCEEDED twiceCapacity 4 observes lower bound 5, retains no partial set, and gets exact total 8 from provider COUNT.
isolation: 0, 0, 3BUILD, BUILD, HITBuyer changes create a separate one-ID set; returning to the original predicate reuses its set. This tests predicate identity, not tenant authorization.
expiry: 0, 3BUILD, BUILDThe helper uses a one-second TTL and actually waits about 1.3 seconds before reusing the same query key.
invalidate: 0, 3, 6BUILD, HIT, BUILDInvalidate the actual observed key between the second and third requests.

The full native names are ID_SET_BUILD, ID_SET_HIT and ID_SET_FALLBACK_LIMIT_EXCEEDED. observedCount=5 in the overflow branch is a lower bound from the bounded identity scan, not the page's exact matching total or a complete retained set. Inspect the language-specific exact-count carrier above. Expiry/invalidation rebuild the same matching query; do not demonstrate a rebuild by silently changing the namespace instead.

Retained membership is not a transactional snapshot of every row value. These reads run over an unchanged fixture; the evidence does not define behavior for simultaneous inserts, deletes, reordering, distributed stores or cross-process retention. Do not advertise those guarantees based on the in-memory example.

Run the retained-ID sequence on its own data​

Continue in the prepared application directory. Switch to a new database and the absolute insights-fixture path saved by the export setup:

export ORDER_DATABASE="$(mktemp -d)/insights.sqlite"
export ORDER_WORKFLOW_SERVICE_CORE_DATABASE_URL="sqlite://$ORDER_DATABASE"
export ORDER_FIXTURE="$ORDER_INSIGHTS_FIXTURE"
run_order_phase bootstrap
run_order_phase seed
export ORDER_RETAINED_MODE=jumps
run_order_phase insights-retained

The report is insights-retained, with pages and events. Set another ORDER_RETAINED_MODE and invoke the same phase to inspect the corresponding read-only sequence. Each invocation owns a new context/store; the expiry and invalidation actions occur within that invocation, not across separate shell commands. Keep the store helper when adapting this source; copying only its Q request does not reproduce these lifecycle observations.

3. Summarize the same filtered Order set​

Use the ten-order insights database. The application repeats buyer, currency and minimum total on each grouped/overall aggregate and on each Facet request that also supplies the visible parent page.

Grouped provider results:

let groups=Q::customer_orders().with_buyer_reference_is(buyer.as_str()).with_currency_code_is(currency.as_str()).with_total_minor_greater_than_or_equal_to(minimum)
.group_by_order_status_as("statusId").count_as("count").sum_total_minor_as("totalMinor").limit(4)
.comment("Summarize filtered Order amounts by status").purpose("Compute same-currency Order summaries in the provider").execute_for_rows(context).await?;

Separate overall aggregate:

let summary=Q::customer_orders().with_buyer_reference_is(buyer.as_str()).with_currency_code_is(currency.as_str()).with_total_minor_greater_than_or_equal_to(minimum)
.count_as("count").sum_total_minor_as("totalMinor").limit(1)
.comment("Summarize the complete filtered Order set").purpose("Compare exact counts and money independent of page size").execute_for_rows(context).await?;

The provider performs the SUM and GROUP BY. The application only converts fields for the shared report; it does not sum the visible page to invent a grand total. Money in these fixtures remains integer minor units, with one selected currency.

The request explicitly aliases the status key as statusId. Analytic values come from CompactRow through get(key) and try_f64(), as shown by the application aggregate_number helper. The overall totalMinor uses an optional conversion so a provider NULL stays None/JSON null. Do not use typed Order E expressions to read these analytic rows.

Facets and bounded row previews​

The existing loop runs this Facet request with all-status buckets and then with matched-only buckets:

let page=Q::customer_orders().with_buyer_reference_is(buyer.as_str()).with_currency_code_is(currency.as_str()).with_total_minor_greater_than_or_equal_to(minimum)
.facet_by_order_status_as_with_options("statuses",Q::order_statuses_minimal().select_code().count_customer_orders_as("count").limit(4),all)
.select_order_item_list_with(Q::order_items().order_by_sku_code_asc().order_by_id_asc().limit(1)).order_by_id_asc().limit(3).count()
.comment("Read Order status buckets and bounded item previews").purpose("Keep rows facets and counts on the same filtered set").execute_for_list(context).await?;

This request returns at most three Orders, with one item preview per parent. The overall count/sum and status buckets describe the whole filtered set. In the all-USD case the visible page has three Orders (stored parent totals sum to 13100), while the complete matching set has eight Orders totaling 35350 cents. Neither a page sum nor a sum of Notebook previews is the matching-set amount.

Buyer / currency / minimumGrouped status totals (count, cents)Overall aggregate
Export / USD / 0DRAFT (3, 11850), CONFIRMED (2, 10400), CANCELLED (3, 13100)Count 8, sum 35350.
Export / USD / 5000CONFIRMED (2, 10400), CANCELLED (1, 5200); no DRAFT group row.Count 3, sum 15600.
Control / USD / 0CONFIRMED (1, 3950).Count 1, sum 3950.
Export / EUR / 0DRAFT (1, 3950).Count 1, sum 3950 euro cents, not USD.
Missing buyer / USD / 0Empty grouped result.One overall row: count 0, sum null.

For minimum 5000, all-status Facets retain DRAFT:0, while matched-only Facets omit DRAFT. For a missing buyer, all three all-status buckets are zero, matched-only buckets and the parent page are empty. Preserve provider NULL for an empty SUM; replacing it with zero is an explicit presentation decision, not this recorded aggregate result. An empty grouped list and a one-row zero-count summary are different successful result shapes.

Read the summary report​

The retained-ID recipe already prepared the insights database; these calls only read it:

export ORDER_SUMMARY_BUYER=BUYER-EXPORT
export ORDER_SUMMARY_CURRENCY=USD
export ORDER_SUMMARY_MINIMUM=0
run_order_phase insights-summary
export ORDER_SUMMARY_MINIMUM=5000
run_order_phase insights-summary

For the other table rows, change all three inputs deliberately: use BUYER-CONTROL, BUYER-MISSING, or currency EUR as appropriate, resetting the minimum to 0. The helper accepts console inputs; a service must validate its currency/amount/scope inputs before building the request. Do not merge USD and EUR amounts or expect a three-row UI limit to change the full-set summary.

Diagnose plans and result shapes​

SymptomInspect firstRecovery / correct interpretation
Nine export-buyer rows rather than eightWhich fixture was seeded, and whether the export request filters currency.Use the nine-order export fixture for this recipe; the ten-order fixture adds an export-buyer EUR control.
Every page looks like a fresh first pageActual offset, same-context lifetime and the native observation immediately after that request.Keep one sequence context; a new process starts a new in-memory sequence. Preserve the recorded workspace runtime when diagnosing observation differences.
Offset 9 falls back or returns no rowsPage rows versus exact total.The export tail's CACHE_MISS and total 8 are expected. A retained-ID empty tail can still be a HIT.
Repeated BUILD after changing modeNamespace, context/store recreation and query predicate.Demonstrate reuse within the same sequence; separate processes/modes deliberately create fresh state.
observedCount=5, total=8Capacity-4 overflow, native fallback and provider COUNT.Keep the lower bound and exact total distinct; do not cache a partial set or return 5 as the total.
Hit returns another buyer's IDsTyped predicate key and exact runtime/source/package identity.Use the qualified scope and the recorded condition-key fix where needed; do not hide a key-collision defect by omitting isolation predicates.
Empty sum is missing or throws a cast errorThe overall analytic carrier/alias and native nullable numeric conversion.Preserve the one-row count 0/sum null result; use this language's analytic values, not typed Order accessors or required non-null casts.
Facets or sum disagree with the visible pageFilters and difference between full matching set, parent limit and child preview.Repeat buyer/currency/minimum on every request; compare totals for the full set rather than summing a truncated preview.

Existing evidence and continuation​

The pagination receipt retains the original bounded/export campaign. The core insights receipt reruns export plus retained-ID and summary cases with the exact source quoted here. Inspect runtimes.rust.verification.cases.continuous-pages, retained-id-pages and order-analytics: native reports/plans, store keys/events, provider SQL and unchanged full snapshots supply different parts of the explanation.

These are local SQLite, fixed-data reads with the recorded runtime and application adapters. Existing published failures, pending-release marks, generator repairs pending deployment, School and every earlier Order receipt remain scoped and unchanged. See the Rust troubleshooting guide, query API guide and coverage matrix.

Continue with buyer and tenant isolation​

Use Rust Order Isolation to upgrade the model to v1.1 and build root/buyer scope from an application-owned trusted session.