Skip to main content

Running Queries

db.query() is the convenient one-shot path, but DortDB exposes each stage of the pipeline so you can inspect or transform a query along the way. The stages are:

  1. parse: query text → AST nodes.
  2. buildPlan: AST → an optimized logical plan of algebra operators.
  3. executePlan: plan → results (streamed lazily).

query() simply runs all three and materializes the output.

One-shot: query()

const result = db.query('SELECT a FROM t');
result.data; // materialized array
result.schema; // ['a']

Use this whenever you just want results. Note that if a query text contains multiple statements, only the last statement is executed.

Step by step

parse

parse returns an array of AST nodes, one per top-level statement:

import { ASTNode } from '@dortdb/core';

const ast: ASTNode[] = db.parse('SELECT a FROM t');
const last = ast.at(-1);

buildPlan

buildPlan translates one AST node into a logical plan and runs the optimizer on it. The returned object is a plan operator tree; every operator exposes getChildren() for quick traversal, and both AST nodes and plan operators implement the visitor pattern for more structured processing.

import * as plan from '@dortdb/core/plan';

const p = db.buildPlan(ast.at(-1));

// Example: flip every ORDER BY direction without writing a full visitor
const stack = [p];
while (stack.length) {
const current = stack.pop();
stack.push(...current.getChildren());
if (current instanceof plan.OrderBy) {
for (const o of current.orders) {
o.ascending = !o.ascending;
}
}
}

executePlan

executePlan runs a plan and returns a result whose data is a lazy iterable rather than a materialized array:

const result = db.executePlan(p);

for (const row of result.data) {
// rows are produced on demand
if (done(row)) break; // stop early without computing the rest
}

This is the difference between executePlan and query: query eagerly collects data into an array, while executePlan streams it. Streaming lets you process large results incrementally or stop early. To materialize a streamed result, spread or collect it: const rows = [...result.data].

Synchronous execution

Execution is synchronous and single-threaded; iterating data pulls rows through the operator tree on the calling thread. Functions and aggregates used in queries must therefore be synchronous.

Bound parameters

Pass runtime values into a query with boundParams instead of interpolating them into the query string. Parameters are referenced by name; the syntax depends on the language:

  • SQL uses :name (or ?name).
  • Cypher and XQuery use $name.
// SQL
db.query('SELECT n FROM nums WHERE n > :threshold', {
boundParams: { threshold: 15 },
});

// Cypher
db.query('MATCH (a) WHERE a.id > $id RETURN a', {
mainLang: 'cypher',
boundParams: { id: 13 },
});

boundParams works with query, and the underlying executePlan accepts the same parameter map as its second argument.

Summary

MethodReturnsDataUse when
query(text, opts?)QueryResultmaterialized arrayyou just want results
parse(text, opts?)ASTNode[]n/ayou need the AST
buildPlan(ast, opts?)PlanOperatorn/ayou want to inspect/transform the plan
executePlan(plan, params?)QueryResultlazy iterableyou want streaming / early exit