Guides: Command line
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 solverThe sqlib
command line
sqlib runs SPARQL queries, ETL jobs and SPARQL RL rule
sets in one process, with no sqlib server. It is built for pipelines: a
container that reads rows, converts them to RDF, infers over them and
writes the result, with no HTTP between the steps.
It runs the same code the server runs. A query is compiled by the
server's parser and applied by the export runtime's splice; an ETL job
goes through the server's ETL loop; a rule set goes through the server's
rule runner. Two tests hold that true:
packages/api/test/lib/etlParity.test.ts and
packages/api/test/lib/srlParity.test.ts.
Running it
The packages are not on npm yet. They are ready to publish, and pass
the same install checks as the static-export runtime; once they are,
npm install -g @sparql-query-lib/cli installs the command.
Two ways to run it today:
- From this repository.
pnpm install && pnpm build, thennode packages/cli/dist/cli.js <command>. - In a container. Build
examples/docker/cli-minimalfrom the repository root; see Container images below.
It needs Node 22.4 or later. DuckDB's native binding is installed with it.
Commands
Every command reads - as standard input and writes to
standard output unless --out names a file. Logs and
progress go to standard error, so a command's output can be piped into
the next. Each command takes --help.
sqlib query
sqlib query --file by-city.rq --data people.ttl --arg 'city=<http://ex/Perth>' --limit 1=20Runs a query from a file. Parameters work as they do on the server: a
VALUES ?x { UNDEF } clause is a slot,
LIMIT 0001 is a named limit.
--arg var=termadds one row to a one-variable slot; repeat it for more rows. A term is written as in SPARQL:<http://…>,"text","text"@en,"1"^^<datatype>, or a bare number or boolean.--args file.jsontakes argument sets as SPARQL Results JSON, for a slot that binds several variables. The format is the onePOST /executetakes.--limit name=nand--offset name=nfillLIMIT 000nandOFFSET 000n.--endpoint urlruns against a SPARQL endpoint. Otherwise--datafiles are loaded into an in-process Oxigraph store; the format comes from the file name, or from--data-format.--textprints the substituted query instead of running it.
SELECT and ASK print SPARQL Results JSON. CONSTRUCT and DESCRIBE
print RDF, in --format (Turtle by default).
sqlib etl run
sqlib etl run --job job.json --out customers.ntRuns an ETL job: the SQL runs in DuckDB, its rows are read in chunks,
each chunk fills the CONSTRUCT template's VALUES slot, and
each chunk's RDF is written as soon as it exists. A run holds one
chunk's RDF at a time, whatever the source's size.
The job file is JSON, in the shape of a stored ETL job:
{
"sql": "SELECT * FROM read_csv('customers.csv')",
"templateFile": "construct.rq",
"chunkSize": 1000,
"columns": [
{ "columnName": "id", "targetVariable": "c", "termType": "uri",
"iriTemplate": "http://ex/c/{value}", "nullPolicy": "skipRow" },
{ "columnName": "name", "targetVariable": "name", "termType": "literal",
"nullPolicy": "undef" }
]
}sqlorsqlFile, andtemplateortemplateFile. File names in the job are relative to the job file. Paths inside the SQL are DuckDB's, so they are relative to the directorysqlibruns in.- The template is a CONSTRUCT with exactly one
VALUES (…) { (UNDEF …) }slot, which declares every column'stargetVariable. --formatisn-triples(default),n-quadsorturtle. Each chunk is written as it comes, and chunks concatenate safely in all three: Oxigraph writes no prefix declarations and gives every blank node a random label.--chunk-size,--max-rowsand--dry-runwork as on the server.--endpoint urlruns the CONSTRUCT on an endpoint instead of in process. The template reads only its ownVALUESrows, so it rarely needs one.
DuckDB access. The server keeps every capability off by default, because submitted SQL reaches the server's host. The CLI runs in your own process and reading local files is its job, so:
| Flag | Default | Grants |
|---|---|---|
--allow-filesystem /
--no-allow-filesystem |
on | local file reads and writes |
--allow-http |
off | http(s) reads; implies filesystem, as in the server |
--allow-extension-install |
off | DuckDB installing extensions |
--unrestricted |
off | no sandbox at all |
--memory-limit |
1GB |
DuckDB's memory_limit; empty for none |
--query-timeout |
300000 |
milliseconds per chunk; 0 for none |
The resolved profile is printed to standard error at the start of
each run. An extension already installed loads under the default
profile: start the SQL with LOAD azure;, for example. What
each setting enforces is in the ETL guide.
sqlib rules run
sqlib rules run --srl rules.srl --data base.ttl --out inferred.ntRuns an SRL document: its DATA blocks once, then each stratum of
rules to its own fixpoint. The --data files are the base
graph; the output is the inference graph, the triples the rules and DATA
blocks added.
- Rules are layered with
srl's stratifier. A document that does not stratify is refused, naming the cycle. --max-iterationsis passes per stratum (default 100).--formatisn-triples(default),n-quadsorjson-ld.--traceprints each rule's effect per pass to standard error.
Exit status 0 means converged. Status 2 means a stratum hit
--max-iterations or the rules cycle; the output is then the
graph so far.
sqlib pull and
--bundle
SQLIB_TOKEN=… sqlib pull --from https://sqlib.example.org/api \
--library urn:sqlib:library:main --out lib.json
sqlib check lib.json
sqlib etl run --bundle lib.json --job customers --out customers.nt
sqlib rules run --bundle lib.json --rule-set ancestors --data customers.ntsqlib pull fetches a library's export bundle from a running server and
writes it to a file. Export… on the web app's main page
downloads the same file. A run from that file then needs no server. The
bundle carries the library's queries, its ETL jobs and its rule
sets:
- A query is compiled, exactly as for the static runtime.
- An ETL job carries its SQL, its column mapping, its chunk size and its CONSTRUCT template, compiled. Its backend stays behind: where the SQL reads from and where the RDF goes are decided where the job runs.
- A rule set carries the SRL document the server exports for it, DATA blocks included.
--bundle <file> with --query,
--job or --rule-set <key> runs one
entry. sqlib check lib.json validates a bundle, verifies
its hashes and lists the keys. A bundle someone edited by hand fails its
hash check and is refused.
pull options:
--tag iri[,iri]and--match any|allexport a subset, as the export route does. ETL jobs carry no tags, so a tag filter leaves them all out.--no-queries,--no-groups,--no-etland--no-rule-setsleave those members out. A query group still carries the queries it runs. Turning all four off is refused before the server is asked.- A bearer token comes from the environment variable
SQLIB_TOKEN, or the one--token-envnames. It is never an argument, because arguments land in shell history and process lists.
What the server could not export is reported on standard error, one
line for each entry, with the reason: a job whose template is not a
CONSTRUCT, say, or a rule set with a data block that has no
DATA form.
Run reports
etl run and rules run take
--report <file>. After the run, the file holds a JSON
record of it, because a run the server never saw leaves no execution
record behind:
- what ran: the bundle and key or the file, the template's and the SQL's hashes, and the server's job, version and mapping IRIs;
- for a job: the DuckDB profile and limits, the chunk size, rows and chunks;
- for a rule set: passes and triples inferred;
- the output's path, format, size and SHA-256;
- start and end times, and milliseconds for each stage
(
duckdbInit,extract,constructandwritefor a job;runandwritefor a rule set); - how it ended:
ok,dry-run,converged,maxIterations,cycleorfailedwith the error.
A failed run still writes its report. --report - writes
it to standard output, and then --out has to name a
file.
sqlib check
sqlib check queries/*.rq rules/*.srlParses each file and reports what sqlib would refuse, without running
it. For a query: its form, its parameters, and whether it compiles to a
template. For SRL: whether it parses, is well formed and stratifies. For
a bundle (.json): its shape, its hashes, whether its rule
sets compile, and its keys. Exit status 1 if any file has a problem, so
it fits a CI step.
Pipelines
Commands compose through files and pipes. Rows to RDF, then inference:
sqlib etl run --job customers.json \
| sqlib rules run --srl customers.srl --data - --data-format nt --out inferred.ntContainer images
examples/docker/ holds three examples, none of them the
published image:
cli-minimalbuilds the CLI from this repository onnode:26-bookworm-slim. CI builds it and runs each command in it on the files insample/.cli-duckdb-azureadds DuckDB'shttpfsandazureextensions at build time, so a job can readaz://paths without the sandbox ever allowing a download. Run jobs with--allow-httpandLOAD azure;in the SQL.cli-azcopyaddsazcopyand a script that copies rows in, runs the job and copies the RDF out. DuckDB never sees the blob store.
The two Azure examples need a storage account, so CI builds neither.
What it does not do yet
- Run a query group. Pipe one command into the next instead; see Pipelines.
- Send a run report anywhere. Collect the files
--reportwrites. - Merge chunks into one Turtle document with prefixes. Each chunk is valid on its own and the chunks concatenate, but the result has no prefix declarations.