Skip to content

Qirava DMS docs

Reading: streaming SELECT, aggregates, early-stop limit

For reads that do not need a join, search, traversal, or vector pass, the engine executes in a single streaming pass over snapshot row references — it folds aggregates, early-stops on LIMIT, and clones only the rows it actually returns. COUNT(*) without a filter is answered O(1) from the row count. SUM/AVG/MIN/MAX fold over the matching iterator without materializing a row buffer for the lone-COUNT case. ORDER collects references, sorts them, and truncates. This page describes those streaming strategies and the exact aggregate output shapes.

When the streaming path is used#

The streaming executor runs whenever the query has NO joins, NO SEARCH, NO TRAVERSE, and NO NEAREST. It reads over row references under one snapshot read lock, choosing the access path (primary point / secondary index / scan) once and reusing it. Join/search/traverse/vector queries fall through to a separate materializing path.

COUNT(*) is O(1) when unfiltered#

A query whose single projection is count(*) is handled specially. With no WHERE it returns the table's row count directly (O(1), no row materialization). With a WHERE it counts the matching iterator without buffering it. count(field) counts rows where that field is present. The result is one object; the key is count_all for count(*) or count_<field> for count(field).

Request

SELECT count(*) FROM t              -- O(1), all rows
SELECT count(*) FROM t WHERE g = 1 -- streams matches

Response

// SELECT count(*) FROM t           -> { "error": null, "data": [ { "count_all": 50 } ], "root": { "count": 1 } }
// SELECT count(*) FROM t WHERE g=1 -> { "error": null, "data": [ { "count_all": 10 } ], "root": { "count": 1 } }

SUM / AVG / MIN / MAX (fold)#

When every projection item is an aggregate, the engine folds the matching rows into one result object. SUM/AVG accumulate plain integers in native i128 for speed and fall back to arbitrary precision only on overflow or non-integer values — so decimals stay EXACT (no floating-point error). AVG divides at 12 fractional digits of precision. MIN/MAX use cross-type numeric comparison. Output keys are sum_<field>, avg_<field>, min_<field>, max_<field>, and count_all/count_<field>.

Request

-- inserted amt values: 0.1, 0.2, 0.3
SELECT count(*), sum(amt), min(amt), max(amt), avg(amt) FROM t

Response

{
  "error": null,
  "data": [ {
    "count_all": 3,
    "sum_amt": "0.6",
    "min_amt": "0.1",
    "max_amt": "0.3",
    "avg_amt": "0.200000000000"
  } ],
  "root": { "count": 1 }
}

LIMIT early-stop and ORDER collect+truncate#

  • LIMIT without ORDER: the matching iterator is consumed with .take(k), so the scan stops as soon as k matches are found — no full scan.
  • No ORDER, no LIMIT: stream all matches.
  • ORDER (with or without LIMIT): collect the matching references, sort them by the order keys, truncate to the limit, then clone only the kept rows into the output.

Request

-- rows: alice/30, bob/25, carol/40
SELECT name, age FROM users WHERE age >= 30 SORT age DESC

Response

{
  "error": null,
  "data": [ { "name": "carol", "age": 40 }, { "name": "alice", "age": 30 } ],
  "root": { "count": 2 }
}

Sort ordering rules#

SORT field [ASC|DESC] defaults to ASC. Multiple sort keys are comma-separated and applied left-to-right. Rows missing a sort field sort AFTER rows that have it (nulls-last): in the comparator a present value is Greater than an absent one. Values compare by the same cross-type numeric/native ordering used in WHERE.

text
SELECT * FROM users SORT age DESC, name ASC