Skip to Content

Spiral resolves scalar, aggregate, async scalar, source, generator, and logical relation functions through session catalogs. The exact function set comes from the active session, not from a hard-coded SQL list in the parser.

For an exhaustive generated list of the default public catalog, see the SPQL function reference.

Unqualified scalar names are mapped into the spql. namespace when possible:

SELECT add(a, b) FROM t; SELECT spql.add(a, b) FROM t;

If a function cannot be resolved, the binder returns unknown SPQL function.

Function calls support positional arguments and named arguments:

SELECT add(left => amount, right => 5) AS amount_plus_five FROM events;

Named arguments are matched against the registered parameter names for the selected overload. A duplicate name or unknown parameter name is rejected.

Current call modifier limitations:

  • Function parameters are not supported.
  • DISTINCT, FILTER, ORDER BY, and OVER clauses on function calls are not supported.

Python and Rust APIs

The generated Python and Rust relation APIs are catalog-backed. The default Python session and generated codegen session register the public plugins, so namespaces such as spiraldb.ql.agg, spiraldb.ql.meta, spiraldb.ql.arrow, spiraldb.ql.parquet, and spiraldb.ql.vortex come from the same function catalog used by SQL.

In Python, scalar calls can execute eagerly over scalars or Arrow arrays, aggregate wrappers build aggregate expressions, relation functions construct relations, and generator functions can either construct a relation from scalar arguments or expand an input relation:

import spiraldb sp = spiraldb.Spiral() events = sp.values({"region": ["east", "east", "west"], "amount": [1, 2, 3]}) scalar_value = spiraldb.ql.add(1, 2) summary = events.aggregate( {"total": spiraldb.ql.agg.sum(spiraldb.path("amount"))}, group_by="region", ) catalog = spiraldb.ql.meta.scalar_functions() standalone_range = spiraldb.ql.range(10) expanded = sp.values({"stop": [2, 3]}).range(spiraldb.path("stop"))

In Rust, generated scalar wrappers return Expr, aggregate wrappers return AggregateExpr, relation functions take &SpiralSession, and generator wrappers return Relation:

use spiral_engine::relation::generated; use spiral_engine::relation::generated::RelationExt as _; use spiral_engine::relation::path; use spiral_engine::relation::RelationSessionExt as _; let events = session.relation("events"); let stops = session.relation("stops"); let expr = generated::add(path("amount"), 1); let summary = events.aggregate( ["region"], [("total", generated::agg::sum(path("amount")))], ); let catalog = generated::meta::scalar_functions(&session); let standalone_range = generated::range(&session, 10); let expanded = stops.range(path("stop"));

Scalar Functions

The default CLI session registers these core scalar families:

SQL spellingSPQL function
a + b / add(a, b)spql.add
a - b / sub(a, b)spql.sub
a * b / mul(a, b)spql.mul
a / b / div(a, b)spql.div
=, <>, <, <=, >, >=spql.eq, spql.neq, spql.lt, spql.lte, spql.gt, spql.gte
AND, OR, NOTspql.and, spql.or, spql.not
IS NULLspql.is_null
searched CASE WHEN ... THEN ... ELSE ... ENDspql.case_when

Extension plugins can register additional scalar functions. For example, the default CLI session registers Blob and image scalar functions through the IO and image plugins.

Async scalar overloads lower through AsyncProject. They are supported in WHERE, projection expressions, aggregate arguments, HAVING, and generated GROUP BY or ORDER BY helper expressions. The current physical node executes async calls sequentially per morsel; batching and concurrency hints are not consumed yet.

Aggregates

Common aggregate aliases map to SPQL aggregate functions:

SQL nameSPQL function
countspql.agg.count
sumspql.agg.sum
firstspql.agg.first
lastspql.agg.last
minmaxspql.agg.minmax

count(*) is supported. The generated Python and Rust aggregate APIs expose this spelling as agg.count_star().

Aggregate calls can also use named arguments:

SELECT sum(value => amount) AS total FROM events;

Current aggregate limitations:

  • DISTINCT aggregate arguments are not supported.
  • Aggregate FILTER, ORDER BY, and window OVER clauses are not supported.
  • Aggregate calls in projection must be direct projection items, such as sum(score) AS total_score, not nested expressions such as sum(score) + 1.

Table functions

Table functions are relation-producing functions used in FROM:

SELECT * FROM spql.meta.table_functions();

The built-in spql.meta.table_functions() function lists registered logical relation functions, including visibility, parameters, and summary text. Source functions such as local file readers are intentionally separate and listed by spql.meta.source_functions().

The default CLI session registers:

FunctionCurrent behavior
spql.meta.table_functions()Lists registered logical relation functions.
spql.meta.source_functions()Lists registered source functions.
spql.meta.scalar_functions()Lists registered scalar functions.
spql.meta.aggregate_functions()Lists registered aggregate functions.
spql.meta.extension_types()Lists registered extension dtypes.
spql.meta.copy_formats()Lists registered COPY TO output formats.
spql.vortex.read(path)Source function that reads one local Vortex file through the Vortex scan path.
spql.arrow.read_ipc(path)Reads an Arrow IPC file into a Values logical plan.
spql.parquet.read(path)Source function that reads a Parquet file through a scan source with projection, filter, and limit pushdown.
hf.dataset(repo, config, split, revision)Produces a one-row Hugging Face dataset handle/provenance relation.

Table-function arguments can be positional or named. Each function still owns its own binding contract; source functions that need schema discovery usually require literal path strings.

Spiral Extensions use the same registry to expose modality-specific readers and transforms.

Generator functions

Generator functions produce zero or more rows for each input row. spql.range is the built-in generator:

SELECT * FROM spql.range(10);

Generator functions can also be used laterally when each input row supplies the arguments:

SELECT s.stop, r.value FROM stops AS s CROSS JOIN LATERAL spql.range(s.stop) AS r;

In Python and Rust relation APIs, standalone generator wrappers construct a relation from scalar arguments, while relation methods expand an existing relation using expressions over that relation.

Last updated on