Bounds and history

A query accepts four bounds that protect the engine. bind reads the database at an earlier transaction, or over facts that you supply for one query. Each example reads the same five entities as Queries.

Bounds

Each bound is a member of the query document. Zero gives no explicit bound. Queries still receive a dynamic cost budget when maxCost is zero. Each bound has its own rejection.

BoundLimitsRejection
maxCostCPU time on the query's execution thread, in 100-nanosecond ticksWork_Limit_Exceeded
maxResultsthe number of rows in the resultResult_Limit_Exceeded
maxBytesthe size of the resultByte_Limit_Exceeded
maxWorkRowsthe rows that the engine handlesWork_Rows_Limit_Exceeded

maxResults counts the rows that remain after orderBy, distinct and the window clauses. The engine rejects five result rows against a bound of two.

dust109B
find       [?name]
where      [[?e salary ?sal]
            [?e name ?name]]
orderBy    [?name]
maxResults 2

Error

Result_Limit_Exceeded

The caller can also give maxCost with the request, beside the document. Stardust then uses the smaller of the two values.

The engine measures CPU time from the start of query execution through result construction. It includes planning, fact scans, expressions, native helpers and rendering on that thread. It excludes time spent waiting and work on other threads. It varies between runs and is neither a bytecode count nor an instruction count.

The public API returns rows, with optional explain records for individual calls. The query-run record stops before later page reads.

A positive maxCost limits this measured time. A clock read has a cost, so the engine schedules its checks. Checks occur less often while the limit is far and more often as the limit comes near. It reads the clock again after rendering.

A query can pass the limit by a small amount before the next check. It can pass it by more when its work suddenly becomes slower or a single helper runs for a long time. Without a positive maxCost, Stardust derives a CPU time budget from query structure and rows handled. The other bounds apply to rows and bytes independently.

Reading an earlier transaction

bind.with.db reads the database as it was. asOf accepts a transaction id, a UTC instant, or a duration relative to now. Transaction ids start at 1.

dust113B
find    [?name]
where   [[?e salary ?sal]
         [?e name ?name]]
orderBy [?name]
bind    {with {db {asOf 6}}}
Result / 55B
[{name Ada} {name Bob} {name Cy} {name Dan} {name Eve}]

The seed patch creates all five entities atomically in transaction 6. A snapshot before that commit holds none of them.

dust26B
bind {with {db {asOf 5}}}
Result / 2B
[]
dust26B
bind {with {db {asOf 1}}}
Result / 2B
[]

A UTC instant selects the newest transaction committed at or before that time. An instant after the last commit selects the head transaction.

dust52B
bind {with {db {asOf {#utc 2027-01-01T00:00:00Z}}}}
Result / 55B
[{name Ada} {name Bob} {name Cy} {name Dan} {name Eve}]

An instant before the first commit gives no facts.

dust52B
bind {with {db {asOf {#utc 2026-08-22T12:00:00Z}}}}
Result / 2B
[]

A duration adds to the current time, so {#dur PT0S} reads the present. A negative duration reads the past, as {#dur -PT5S} does for five seconds ago.

dust36B
bind {with {db {asOf {#dur PT0S}}}}
Result / 55B
[{name Ada} {name Bob} {name Cy} {name Dan} {name Eve}]

asOf, from, and to also accept input variables such as ?timestamp. Supply a transaction id, UTC instant, or duration through parameters. You can save the query before you supply the value. Execution without the required value gives Missing_Parameter.

An asOf query can use a schema clause. The current schema validates each entity's document at the selected transaction. The query excludes entities that do not exist at that transaction.

interval reads a window of transactions instead. It has from and to, and both ends are inside the window. Each end accepts a transaction id, a UTC instant, or a duration. An instant window that resolves inverted gives no facts.

dust97B
bind {with {db {interval {from {#utc 2026-08-01T00:00:00Z}
                          to   29}}}}
Result / 55B
[{name Ada} {name Bob} {name Cy} {name Dan} {name Eve}]

A fourth term in a fact clause binds the transaction of that fact.

dust66B
find    [?name ?tx]
where   [[?e name ?name ?tx]]
orderBy [?name]
Result / 144B
[{name Ada
  tx   {#link 6}}
 {name Bob
  tx   {#link 6}}
 {name Cy
  tx   {#link 6}}
 {name Dan
  tx   {#link 6}}
 {name Eve
  tx   {#link 6}}]

When a temporal read is rejected

db accepts asOf or interval, and it needs one of them.

ConditionReason
asOf 0Transaction ids start at 1
A malformed #utc or #dur valueThe bound is not a valid temporal value
interval with from above to, as transaction idsThe window is empty
asOf and interval togetherThe two forms exclude each other
A member that is not asOf or intervaldb accepts no other member
An interval read with a schema clauseSchema matching requires a single snapshot

Each of these conditions gives one error. This query reads transaction 0:

dust89B
find  [?name]
where [[?e salary ?sal]
       [?e name ?name]]
bind  {with {db {asOf 0}}}

Error

Invalid_Temporal

Facts for one query

bind.with.facts adds facts that the database does not hold. Each key is a UTC timestamp, and its value is a merge patch below entities, keyed by entity id, as Patches describes. Ada has ID 1 in this fixture.

dust165B
find    [?name ?sal]
where   [[?e salary ?sal]
         [?e name ?name]]
orderBy [?name]
bind    {with {facts {'2020-10-30T23:20:20Z' {entities {1 {salary 200}}}}}}
Result / 111B
[{name Ada
  sal  200}
 {name Bob
  sal  100}
 {name Cy
  sal  90}
 {name Dan
  sal  70}
 {name Eve
  sal  80}]

A value replaces the stored field, and null retracts it. A reference field uses {#link <id>}. An id that the database does not hold becomes a new entity for this query alone. Stardust writes none of these facts.

Run it

database query · query run.