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, andOVERclauses 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 spelling | SPQL 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, NOT | spql.and, spql.or, spql.not |
IS NULL | spql.is_null |
searched CASE WHEN ... THEN ... ELSE ... END | spql.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 name | SPQL function |
|---|---|
count | spql.agg.count |
sum | spql.agg.sum |
first | spql.agg.first |
last | spql.agg.last |
minmax | spql.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:
DISTINCTaggregate arguments are not supported.- Aggregate
FILTER,ORDER BY, and windowOVERclauses are not supported. - Aggregate calls in projection must be direct projection items, such as
sum(score) AS total_score, not nested expressions such assum(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:
| Function | Current 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.