Start: Concepts
Start
OverviewConceptsGuides
Run and configureREST API walkthroughCommand lineQuery groupsNotebooksRules and SRLPrefixesTestingMCP clientsPublish as toolsQuery bench in chatStatic exportETLDeployingReference
ConfigurationFeature flagsEntity modelSRL languageScriptsExplanation
ArchitectureVersioningEnvironmentsStorage and cachingSecurity modelRDF PatchInline inputsExamples
Sudoku solverConcepts
This page defines the vocabulary the rest of the documentation uses, and the relationships between the things it names. Read it before the guides: several words in sqlib, "tuple" above all, name more than one thing.
Library
A Library is the container everything else belongs
to. A query, a query group, a rule set, a data graph, a tuple set, an
argument set, a test, a benchmark and an ETL job each belong to exactly
one library. A library may name a defaultBackend, the
backend its queries run on when no other is chosen. One server holds
many libraries, and a library is the unit you export, import and
copy.
Stable pointers and immutable versions
Almost every entity comes in two parts. A stable
pointer — Query, RuleSet,
DataGraph, TupleSet, ArgumentSet,
Test, EtlJob, BenchmarkExperiment
— carries the name, description, tags and a currentVersion
link, and its IRI never changes. An immutable version —
QueryVersion, RuleSetVersion, and so on —
carries the content and a numeric version, and is never
modified in place once saved.
Execution always names a version, so a run is reproducible. Two ways of referring to content follow: a pin is a version IRI and always means the same content; a float is a parent IRI and means whatever that entity's current version is at the time of the call.
Query and QueryVersion
A Query is a stable, named pointer, optionally
naming a defaultBackend. A QueryVersion
holds the SPARQL text (queryString), its query type, the
parameters detected in it, and the inputs and outputs inferred from it.
A query version is callable: it declares parameters, and you
invoke it with arguments.
Parameters
Parameters are declared in the query text itself, not in metadata beside it. There are two forms.
A table parameter is a VALUES clause whose only
row is all UNDEF.
SELECT ?city ?pop WHERE {
VALUES (?city) { (UNDEF) }
?city :population ?pop .
}
That clause declares one parameter with the signature
(?city). The reserved row must be the clause's only row; a
block that merely contains an all-UNDEF row alongside bound
rows is not a parameter. Three such clauses declare three table
parameters, matched in order of appearance.
A named number is LIMIT 000n or
OFFSET 000n. In LIMIT 00010 the
parameter's name is 10 — the digits after the
000. Names are digits only: detection matches
/\bLIMIT\s+000(\d+)\b/i against the query's code, so the
same text in a comment or a string literal declares nothing. The literal
value is what the query runs with when no argument is supplied, so an
unparameterised run still executes.
How values are applied. Each argument for a table parameter is a SPARQL Query Results JSON document, the shape a SELECT returns:
{ "head": { "vars": ["city"] },
"results": { "bindings": [ { "city": { "type": "uri", "value": "http://example.org/Perth" } } ] } }So a query's output, a stored tuple set, or another endpoint's
response can be passed as an argument without being edited. Arguments
are matched to VALUES clauses by the variables in
head.vars, not by position, and a request carries at most
one per clause. Blank nodes are refused, because a blank node label only
means something inside the document it came from. The legacy
typed-literal spelling is read as a literal
with its datatype.
At execution the query is parsed and the reserved all-UNDEF row is
replaced by the supplied rows inside the parsed structure; a named limit
or offset has its number substituted. Argument values are never
concatenated into query text. A supplied row may bind only some of a
clause's variables — an unbound cell stays UNDEF, matching
anything — but an all-UNDEF row cannot be mixed with bound rows in one
argument.
Empty input. A caller says two different things with the same table:
| The request | Means | The clause becomes |
|---|---|---|
| leaves the parameter out | nothing arrived, so do not filter | removed |
| supplies it with zero rows | the empty set arrived, so match nothing | VALUES with zero rows |
An argument that fits no clause is refused, naming its variables and
the clauses the query declares. Inside a query group, the edge feeding a
clause may also carry a whenEmpty policy for when its
upstream produces no rows: unconstrained drops the clause,
propagateEmpty keeps the zero-row block, and
require refuses the run. That policy is the author's, so it
lives on the edge and never in a request.
The query's author can set the same policy on the clause itself, on
the version's input tuple. A clause marked require refuses
a run that leaves it out, sends one all-UNDEF row, or sends
zero rows, so a query that must start from a value never scans the whole
graph. A group edge can choose another closed mode for that clause, but
cannot set unconstrained.
ArgumentSet
An ArgumentSet is the values for a callable's SPARQL
parameters: a table per VALUES clause and a number per
named limit or offset. Each table's rows are stored as one SPARQL
Results JSON string, which is exactly the document a run takes
inline.
An argument set holds no graphs. A query group's data graphs are a
separate input, sent in the run's dataGraphs[] beside its
argument sets, as a rule set's data graph is.
Argument sets belong to a library and are listed library-wide. They
record where they were made as provenance, not as a fence: a set made on
one query can be offered to another, and the server judges it
fits, partial or mismatch against
that callable's signature. A version is immutable, so a test or an MCP
call that pins one is reproducible. A run may supply inline values for
parameters a named set leaves open; supplying both for the same
parameter is refused by name. Argument set versions live under
/argument-sets/:id/versions, the same path segment every
versioned entity uses.
Browser defaults
A query and a query group may name a default argument set, and a query group and a rule set may name a default data graph for each data graph input. The web app selects them when the callable opens and nothing else is picked. They are stored on the server, so everyone using the library gets them.
Execution never applies a browser default.
/execute, rule set execute, tests, benchmarks and MCP run
with exactly the inputs the caller sends: an empty call means "no
arguments", and a default changed in the web app must not change what a
script computes. A caller that wants the default reads
GET /…/:id/browser-defaults and sends it.
QueryGroup
A QueryGroup is a directed acyclic graph of execution steps — the canvas. A QueryGroupVersion snapshots its nodes, its edges and the canvas layout.
Node types are StartNode, EndNode,
QueryNode (a pinned query version),
DynamicQueryNode (a query chosen at execution time),
RuleSetNode, PatchNode and
DuckDbEtlNode. A QueryEdge
connects one node's output to another's input, and its
dataFlowType says what travels along it:
dataFlowType |
Carries | Source I/O → target I/O |
|---|---|---|
CONTROL_FLOW |
nothing; an ordering dependency only | none — an edge of this type with I/O references is refused |
VARIABLE_BINDINGS |
a solution sequence | QueryOutputTuple → QueryInputTuple |
RDF_GRAPH |
triples or quads | TriplesQuadsIO → TriplesQuadsIO |
BOOLEAN |
an ASK result | BooleanIO → BooleanIO |
QUERY_ID |
a single column naming a query to run | QueryOutputTuple → QueryIdInput |
DATASET |
a single column of graph IRIs, used as the target's dataset | QueryOutputTuple → the target query's FROM
/ FROM NAMED (no input port) |
Variables are lined up across an edge by
variableMappings, a { source, target }[] list
of variable names. Unset, the default pairs exact names first
and the remainder by position.
The start node's outputs are the group's own
parameters: one port per table input, plus any graph ports. A group also
takes LIMIT and OFFSET parameters, which are
the union of the named numbers its member query versions declare — a
value for pageSize reaches every node whose query names
pageSize, so two nodes sharing a name share the value and a
node that must page independently renames its parameter.
DataGraph
A DataGraph is reference RDF registered in a
library. A DataGraphVersion stores the serialisation
verbatim (Turtle, N-Triples, and so on) with a triple count and a byte
size.
A data graph is input that goes in and does not come out. It seeds the store a rule set or a hermetic query test runs against, fills a query group's graph port, or hydrates a backend. It is not part of a rule set, so it never appears in an inference graph.
TupleSet
A TupleSet is a named, versioned table of RDF terms
— a solution sequence — stored in a library. A
TupleSetVersion stores its content as a standard SPARQL
Results JSON document, along with columns,
rowCount, byteSize and the
sourceFormat it was imported from (CSV, TSV, SPARQL Results
TSV, SPARQL Results JSON, or saved query results).
A tuple set is tabular input. It is never loaded into a
store: it is spliced into a query's VALUES clause and
consumed. Untyped sources (CSV, plain TSV) import as plain string
literals unless column types are chosen at import. Blank nodes are
rejected at import, because a blank node label is document-scoped and
can join with nothing in the target store.
Graphs and tuples compared
| DataGraph | TupleSet | |
|---|---|---|
| Content | an RDF graph | a table of RDF terms |
| Stored as | the original serialisation, verbatim | normalised SPARQL Results JSON |
| Version records | tripleCount, byteSize |
rowCount, byteSize,
columns |
| At execution | loaded into a store | spliced into a VALUES clause |
| Consumed by | rule sets, group graph ports, backends, test fixtures | queries |
That difference is why tabular input is a parameter and RDF input is data.
Rule, DataBlock and RuleSet
sqlib authors SPARQL RL inference rules in SRL
(SPARQL RL), a textual syntax parsed, compiled and stratified by
packages/srl.
- A Rule is one inference rule: a head and a body.
Its version stores the rule text, the grammar it was written in
(
srlorsparql), whether it parsed, and the normalisedINSERTit compiles to. - A DataBlock is a set of facts that the rule set
infers automatically at the start of evaluation. It compiles to
INSERT DATA, and its triples do appear in the inference graph — which is what distinguishes it from a data graph. - A RuleSet groups them. A
RuleSetVersionholdshasRuleandhasDataBlockas lists of version IRIs, never parent IRIs, so a rule set's behaviour cannot change because some rule got a new current version. It also stores a stratification report: the dependency structure the stratifier found, and whether it could order the rules.
A rule set runs in-process against a base graph, which is a
DataGraph. It has no backend to name: fetching data from a
live endpoint and then running rules over it is a query group, not a
rule set with an endpoint.
Backend
A Backend is where a query executes. Three types:
http, a remote SPARQL endpoint;
oxigraphMemory, an in-process Oxigraph store held in memory
whose mode says whether its contents are serialised to disk
on a checkpoint interval and on shutdown; and
oxigraphEphemeral, an in-process store that is never
serialised and dies with the process.
A backend stores an authEnvKey, the name of an
environment variable group rather than a credential. Credentials are
read from SQLIB_BACKEND_<KEY>_USERNAME /
_PASSWORD, or
SQLIB_BACKEND_<KEY>_AUTH_HEADER, in the server's own
environment, so they never enter the library. Which backend a run uses
is resolved by precedence: the one named on the call, then the query's
defaultBackend, then the library's.
Test
A Test is an invocation plus an expectation, run
once, judged pass or fail. Its subject is a
Query, a QueryGroup or a RuleSet,
and its subjectKind names which; a TestVersion
holds the inputs and the expectation. A test may also record a
criterion, an external manifest entry IRI, for tests seeded
from a conformance suite. Which inputs it may supply depends on its
subject's kind, and the rules are stated once, in
packages/types/src/subjectKinds.ts:
| Subject | Backend on the version | Data graph per case | Argument set per case | SQL fixture |
|---|---|---|---|---|
| Query | yes | yes | yes | no |
| Query group | no | yes | yes | no |
| Rule set | no | yes | no | no |
| ETL job | no | yes | no | yes |
A query is the one kind with an exclusive store: it names a backend or gives every case a data graph to run against hermetically, never both and never neither. A query group's nodes name their own backends, so its test has none to name.
Benchmark
A BenchmarkExperiment measures a callable rather than judging it. Its version holds one or more subject specs, which have the same "a callable plus its inputs" shape as a test's invocation half. A run produces iterations, per-node runs and observations, so timings can be read per node as well as per call.
ETL pipeline
An EtlJob builds RDF from tabular sources. Its
version holds DuckDB sql, a sparqlTemplate
that constructs triples from the rows that SQL returns, the target
backendId, a chunk size, and a pointer to a column-mapping
version; an EtlExecution records a run.
An ETL job declares no parameters: its tabular input is the rows its SQL reads. That is why an ETL test supplies a SQL fixture — DuckDB statements run on the job's own connection, against a database that exists only for that case — rather than an argument set.
ETL is off by default (FEATURE_ETL,
FEATURE_PLAYGROUND_ETL), because arbitrary DuckDB SQL is a
host filesystem read primitive and, with the httpfs extension, an
outbound request
primitive.