Semantics and SPARQL Starter Pack
Parse RDF, load graph data, and query with SPARQL in one practical reference
Graph data, query, and result sit side by side here, so you never lose the thread between parsing triples and querying them. Every executable asset in this pack ran against MarkLogic 12.0.0 using the deployed llamaverse wild-llamas collection — the counts and sample rows below are what actually came back, not tidied-up placeholder data.
This pack covers the Semantics baseline you need before building knowledge-graph features in production: parse RDF safely, insert triples into a named graph, and query that graph with SPARQL. Get comfortable with these three moves and the rest of the Semantics API stops feeling like a separate language bolted onto MarkLogic.
Sample Data and Execution Context
The executable examples query real profile documents from the deployed wild-llamas collection. The parse, SELECT, CONSTRUCT, lifecycle, and binding examples all build their RDF input from bounded live collection queries at execution time, so there is no second fixture to drift away from the database.
The examples in this article assume the llamaverse (v2.5.0+) is deployed. The llamaverse sample data is freely available from github.com/cleverllamas/llamaverse — see the llamaverse article for full setup instructions.
| Context | Value |
|---|---|
| Live source | wild-llamas profile documents queried at runtime |
| Named graph | https://cleverllamas.com/llamaverse/graphs/wild-llamas |
| Persistent API family | sem:graph-insert, sem:graph-delete |
| Query API family | sem:sparql, sem:store, sem:in-memory-store |
The XQuery assets import /MarkLogic/semantics.xqy; the Server-Side JavaScript assets require the same module. Without that import, MarkLogic reports an undefined sem:* function rather than executing the example.
API Context for This Pack
| Context Item | What this pack uses |
|---|---|
| Data source | wild-llamas profile documents and a captured RDF snapshot |
| Graph strategy | Named graph URI (https://cleverllamas.com/llamaverse/graphs/wild-llamas) |
| Primary functions | sem:rdf-parse, sem:graph-insert, sem:sparql, sem:store, sem:graph-delete |
| Output style | Counts plus small sample rows |
Functions in This Pack
sem:rdf-parse + sem:graph-insert
Use this pattern when you want deterministic ingest from RDF text into a named graph.
Source docs: https://docs.marklogic.com/11.0/sem:rdf-parsehttps://docs.marklogic.com/11.0/sem:graph-insert
| Option / Argument | What it controls | Used here |
|---|---|---|
sem:rdf-parse($text, $format) | Parses RDF source text into triples | Turtle text parsed with "turtle" format |
sem:graph-insert($graph, $triples) | Stores triples in the target named graph | Graph URI https://cleverllamas.com/llamaverse/graphs/wild-llamas |
xquery version "1.0-ml";
import module namespace sem = "http://marklogic.com/semantics"
at "/MarkLogic/semantics.xqy";
let $graph := sem:iri("https://cleverllamas.com/llamaverse/graphs/wild-llamas")
let $seedRows :=
for $doc in cts:search(collection("wild-llamas"), cts:true-query())[1 to 300]
let $id := fn:normalize-space(fn:string($doc/id))
let $name := fn:normalize-space(fn:string($doc/name))
let $home := fn:normalize-space(fn:string($doc/placeOfBirth))
where $id ne "" and $name ne "" and $home ne ""
return <row><id>{$id}</id><name>{$name}</name><home>{$home}</home></row>
let $seedRows := subsequence($seedRows, 1, 3)
let $ttl := string-join((
'@prefix llama: <https://cleverllamas.com/llamaverse/llama/> .',
'@prefix schema: <http://schema.org/> .',
'',
for $row in $seedRows
return concat(
'llama:', $row/id, ' schema:name "', $row/name,
'" ; schema:homeLocation "', $row/home, '" .'
)
), " ")
let $triples := sem:rdf-parse($ttl, "turtle")
let $_ := sem:graph-delete($graph)
let $_ := sem:graph-insert($graph, $triples)
return
<result>
<graph>{$graph}</graph>
<seedRowCount>{count($seedRows)}</seedRowCount>
<tripleCount>{count($triples)}</tripleCount>
<samples>{
for $t in subsequence($triples, 1, 3)
return <triple>{sem:triple-subject($t)} {sem:triple-predicate($t)} {sem:triple-object($t)}</triple>
}</samples>
</result>
'use strict';
const sem = require('/MarkLogic/semantics.xqy');
const seedDocs = cts.search(cts.collectionQuery('wild-llamas')).toArray().slice(0, 300);
const seedRows = seedDocs.map((docNode) => ({
id: fn.normalizeSpace(fn.string(docNode.xpath('id'))),
name: fn.normalizeSpace(fn.string(docNode.xpath('name'))),
home: fn.normalizeSpace(fn.string(docNode.xpath('placeOfBirth')))
})).filter((row) => row.id && row.name && row.home).slice(0, 3);
const turtle =
'@prefix llama: <https://cleverllamas.com/llamaverse/llama/> .\n' +
'@prefix schema: <http://schema.org/> .\n\n' +
seedRows.map((row) =>
`llama:${row.id} schema:name "${row.name.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}" ;\n` +
` schema:homeLocation "${row.home.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}" .`
).join('\n\n');
const triples = sem.rdfParse(turtle, 'turtle').toArray();
const store = sem.inMemoryStore(triples);
const query =
'PREFIX schema: <http://schema.org/>\n' +
'SELECT ?name ?home\n' +
'WHERE {\n' +
' ?s schema:name ?name ;\n' +
' schema:homeLocation ?home .\n' +
'}\n' +
'ORDER BY ?name';
const resultRows = sem.sparql(query, null, null, store).toArray();
const results = {
seedRowCount: seedRows.length,
tripleCount: triples.length,
rowCount: resultRows.length,
samples: triples.slice(0, 3).map((triple) => ({
subject: sem.tripleSubject(triple),
predicate: sem.triplePredicate(triple),
object: sem.tripleObject(triple)
}))
};
({
sample: 'sem/semantics-and-sparql-starter-pack/assets/sem-rdf-parse-and-store.sjs',
kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
count: Array.isArray(results) ? results.length : 0,
data: results
});
<result>
<graph>https://cleverllamas.com/llamaverse/graphs/wild-llamas</graph>
<seedRowCount>3</seedRowCount>
<tripleCount>6</tripleCount>
<samples>
<triple>https://cleverllamas.com/llamaverse/llama/67384a21-62fe-4f80-a964-72783cfb076f http://schema.org/name Lisa</triple>
<triple>https://cleverllamas.com/llamaverse/llama/67384a21-62fe-4f80-a964-72783cfb076f http://schema.org/homeLocation Kimberlychester, British Indian Ocean Territory (Chagos Archipelago)</triple>
<triple>https://cleverllamas.com/llamaverse/llama/c45a4f65-d14a-4206-b921-ffc9470e14bb http://schema.org/name David</triple>
</samples>
</result>
| Field | Value |
|---|---|
| graph | https://cleverllamas.com/llamaverse/graphs/wild-llamas |
| seedRowCount | 3 |
| tripleCount | 6 |
| sample triple 1 | Lisa → name → Lisa |
| sample triple 2 | Lisa → homeLocation → Kimberlychester, British Indian Ocean Territory (Chagos Archipelago) |
| sample triple 3 | David → name → David |
What to notice: nothing here is implicit — parse, then insert, then check the count and a sample of what actually landed in the graph. Trust the number, not the intention.
sem:sparql
Use this pattern when you need graph query logic that stays declarative and readable. The paired assets use an in-memory store seeded from live llamaverse profiles, so either language can be run independently; a persistent named-graph query is shown in the store-scope section below.
This example assumes the named graph was populated by the
sem:rdf-parse + sem:graph-insertsample above.
Source docs: https://docs.marklogic.com/11.0/sem:sparql
| Option / Argument | What it controls | Used here |
|---|---|---|
$sparql | SPARQL query text to execute | SELECT query over the named graph |
$bindings | External variable bindings for query variables | Not set in this sample |
$options | SPARQL execution options (base, dataset options, parse/prepare/optimise controls) | Not set in this sample (defaults apply) |
$store | Explicit store source (for example sem:store(...) or sem:in-memory-store(...)) | In-memory store from parsed triples in JavaScript sample |
$options Value | What it controls | Default when omitted |
|---|---|---|
"base=IRI" | Base IRI used for relative IRIs in the query | No explicit base override |
"default-graph=IRI*" | Adds one or more named graphs to the query default graph | No extra default graph additions |
"named-graph=IRI*" | Adds one or more named graphs to available named graph set | No extra named graph additions |
"parse-check" | Parse only; skip static checks and execution | false |
"prepare" | Parse and optimise; do not execute | false |
| `"optimize=0 | 1 | 2"` |
"trace=ID" | Logs query plan/optimization/execution details using identifier ID | Not enabled |
sem:sparql options and store scope: what matters in production
The $options above tune parsing, dataset shaping, and optimisation — they don't touch what the query is allowed to see. That job belongs to the separate $store argument.
When you pass sem:store(...) into $store, you can include a cts:query to constrain the entire triple set available to the SPARQL evaluation. In other words, the cts:query is applied first to choose candidate fragments, and SPARQL runs only against triples from that scoped set.
sem:store reference: https://docs.marklogic.com/11.0/sem:store
Example scoping pattern:
let $store := sem:store(
("document"),
cts:collection-query("wild-llamas")
)
return sem:sparql($sparql, (), (), $store)
const store = sem.store(
['document'],
cts.collectionQuery('wild-llamas')
);
const rows = sem.sparql(sparqlText, null, null, store);
Use this when your query should only see triples from a controlled subset of content (for example one collection, directory, or security-partitioned slice of data).
xquery version "1.0-ml";
import module namespace sem = "http://marklogic.com/semantics"
at "/MarkLogic/semantics.xqy";
let $triples :=
for $doc in cts:search(collection("wild-llamas"), cts:true-query())[1 to 300]
let $id := fn:normalize-space(fn:string($doc/id))
let $name := fn:normalize-space(fn:string($doc/name))
let $home := fn:normalize-space(fn:string($doc/placeOfBirth))
where $id ne "" and $name ne "" and $home ne ""
return (
sem:triple(sem:iri("https://cleverllamas.com/llamaverse/llama/" || $id), sem:iri("http://schema.org/name"), $name),
sem:triple(sem:iri("https://cleverllamas.com/llamaverse/llama/" || $id), sem:iri("http://schema.org/homeLocation"), $home)
)
let $store := sem:in-memory-store($triples)
let $query :=
'PREFIX schema: <http://schema.org/>
SELECT ?name ?home
WHERE {
?s schema:name ?name ;
schema:homeLocation ?home .
}
ORDER BY ?name'
return sem:sparql($query, (), (), $store)
'use strict';
const sem = require('/MarkLogic/semantics.xqy');
const seedDocs = cts.search(cts.collectionQuery('wild-llamas')).toArray().slice(0, 300);
const triples = [];
seedDocs.forEach((docNode) => {
const id = fn.normalizeSpace(fn.string(docNode.xpath('id')));
const name = fn.normalizeSpace(fn.string(docNode.xpath('name')));
const home = fn.normalizeSpace(fn.string(docNode.xpath('placeOfBirth')));
if (id && name && home) {
const subject = sem.iri(`https://cleverllamas.com/llamaverse/llama/${id}`);
triples.push(
sem.triple(subject, sem.iri('http://schema.org/name'), name),
sem.triple(subject, sem.iri('http://schema.org/homeLocation'), home)
);
}
});
const store = sem.inMemoryStore(triples);
const query =
'PREFIX schema: <http://schema.org/>\n' +
'SELECT ?name ?home\n' +
'WHERE {\n' +
' ?s schema:name ?name ;\n' +
' schema:homeLocation ?home .\n' +
'}\n' +
'ORDER BY ?name';
const rows = sem.sparql(query, null, null, store).toArray();
const results = {
seedRowCount: triples.length / 2,
seedTripleCount: triples.length,
rowCount: rows.length,
rows: rows
};
({
sample: 'sem/semantics-and-sparql-starter-pack/assets/sem-sparql-select.sjs',
kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
count: Array.isArray(results) ? results.length : 0,
data: results
});
{"name": "Aaron", "home": "Port Jacobstad, Zimbabwe"}
{"name": "Alejandro", "home": "North Joseph, Reunion"}
{"name": "Alexander", "home": "East Tina, Kiribati"}
| name | home |
|---|---|
| Aaron | Port Jacobstad, Zimbabwe |
| Alejandro | North Joseph, Reunion |
| Alexander | East Tina, Kiribati |
What to notice: the SPARQL stays compact, and the result bindings read like a plain table — that's the payoff of a declarative query language over hand-rolled graph traversal.
sem:sparql with external bindings
Use this pattern when the query text stays stable but the filter values come from caller input. In this starter-pack example, the values are sourced from real llamaverse documents at runtime, transformed into an in-memory triple store, and then passed as external SPARQL bindings.
| Option / Argument | What it controls | Used here |
|---|---|---|
$bindings | External variable values injected into the SPARQL evaluation | homeFilter derived from seeded llamaverse rows |
| In-memory store seed step | Data loaded before executing the binding query | wild-llamas collection rows transformed to RDF triples |
xquery version "1.0-ml";
import module namespace sem = "http://marklogic.com/semantics"
at "/MarkLogic/semantics.xqy";
let $seedRows :=
for $doc in cts:search(collection("wild-llamas"), cts:true-query())[1 to 300]
let $id := fn:normalize-space(fn:string($doc/id))
let $name := fn:normalize-space(fn:string($doc/name))
let $home := fn:normalize-space(fn:string($doc/placeOfBirth))
where $name ne "" and $home ne ""
return
<row>
<id>{$id}</id>
<name>{$name}</name>
<home>{$home}</home>
</row>
let $seedRows := subsequence($seedRows, 1, 50)
let $bindingHome := fn:string(($seedRows/home)[1])
let $triples :=
for $r in $seedRows
let $id := fn:string($r/id)
let $name := fn:string($r/name)
let $home := fn:string($r/home)
let $subject :=
sem:iri(
"https://cleverllamas.com/llamaverse/llama/" ||
(if ($id ne "") then $id else xdmp:md5($name || "|" || $home))
)
return (
sem:triple($subject, sem:iri("http://schema.org/name"), $name),
sem:triple($subject, sem:iri("http://schema.org/homeLocation"), $home)
)
let $store := sem:in-memory-store($triples)
let $query :=
"PREFIX schema: <http://schema.org/>
SELECT ?name ?home
WHERE {
?s schema:name ?name ;
schema:homeLocation ?home .
FILTER (?home = ?homeFilter)
}
ORDER BY ?name"
let $bindings := map:new((
map:entry("homeFilter", $bindingHome)
))
let $rows := sem:sparql($query, $bindings, (), $store)
return
<result>
<bindingHome>{$bindingHome}</bindingHome>
<seedRowCount>{count($seedRows)}</seedRowCount>
<seedTripleCount>{count($triples)}</seedTripleCount>
<rowCount>{count($rows)}</rowCount>
<samples>{
for $row in subsequence($rows, 1, 5)
return <row>{xdmp:to-json-string($row)}</row>
}</samples>
</result>
'use strict';
const sem = require('/MarkLogic/semantics.xqy');
const seedDocs = cts.search(cts.collectionQuery('wild-llamas')).toArray().slice(0, 300);
const seedRows = [];
const triples = [];
seedDocs.forEach((docNode) => {
const id = fn.normalizeSpace(fn.string(docNode.xpath('id')));
const name = fn.normalizeSpace(fn.string(docNode.xpath('name')));
const home = fn.normalizeSpace(fn.string(docNode.xpath('placeOfBirth')));
if (name && home && seedRows.length < 50) {
const subjectId = id || xdmp.md5(`${name}|${home}`);
const subject = sem.iri(`https://cleverllamas.com/llamaverse/llama/${subjectId}`);
seedRows.push({ id: subjectId, name: name, home: home });
triples.push(sem.triple(subject, sem.iri('http://schema.org/name'), name));
triples.push(sem.triple(subject, sem.iri('http://schema.org/homeLocation'), home));
}
});
const bindingHome = triples.length > 0
? sem.tripleObject(triples[1]).toString()
: 'UNKNOWN';
const store = sem.inMemoryStore(triples);
const query =
'PREFIX schema: <http://schema.org/>\n' +
'SELECT ?name ?home\n' +
'WHERE {\n' +
' ?s schema:name ?name ;\n' +
' schema:homeLocation ?home .\n' +
' FILTER (?home = ?homeFilter)\n' +
'}\n' +
'ORDER BY ?name';
const bindings = { homeFilter: bindingHome };
const rows = sem.sparql(query, bindings, null, store).toArray();
const results = {
bindingHome: bindingHome,
seedRowCount: seedRows.length,
seedTripleCount: triples.length,
rowCount: rows.length,
samples: rows.slice(0, 5)
};
({
sample: 'sem/semantics-and-sparql-starter-pack/assets/sem-sparql-select-bindings.sjs',
kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
count: Array.isArray(results) ? results.length : 0,
data: results
});
seedRowCount: 50
seedTripleCount: 100
rowCount: 1
sample: one real llamaverse name whose home equals the bound home value
| Field | Value |
|---|---|
bindingHome | A home value selected from the live llamaverse run |
seedRowCount | 50 |
seedTripleCount | 100 (the JavaScript run also selects 50 valid rows) |
rowCount | 1 |
| Sample row | One matching name and home from that run |
What to notice: treat external bindings as the default, not the exception — string-concatenating SPARQL filters in application code is how injection bugs get written. This sample uses live llamaverse values, so the result rows are directly tied to real repository data. The exact person and place can change as the collection changes; the execution output is authoritative.
sem:sparql (CONSTRUCT)
Use this pattern when you want a shaped graph result rather than just tabular bindings.
Source docs: https://docs.marklogic.com/11.0/sem:sparql
| Option / Argument | What it controls | Used here |
|---|---|---|
$sparql | SPARQL query text to execute | CONSTRUCT query producing triples |
$bindings | External variable bindings for query variables | Not set in this sample |
$options | SPARQL execution options (base, dataset options, parse/prepare/optimise controls) | Not set in this sample (defaults apply) |
$store | Explicit store source (for example sem:store(...) or sem:in-memory-store(...)) | In-memory store from parsed triples in JavaScript sample |
xquery version "1.0-ml";
import module namespace sem = "http://marklogic.com/semantics"
at "/MarkLogic/semantics.xqy";
let $triples :=
for $doc in cts:search(collection("wild-llamas"), cts:true-query())[1 to 300]
let $id := fn:normalize-space(fn:string($doc/id))
let $name := fn:normalize-space(fn:string($doc/name))
where $id ne "" and $name ne ""
return sem:triple(
sem:iri("https://cleverllamas.com/llamaverse/llama/" || $id),
sem:iri("http://schema.org/name"),
$name
)
let $store := sem:in-memory-store($triples)
let $query :=
"PREFIX schema: <http://schema.org/>
CONSTRUCT {
?s schema:label ?name .
}
WHERE {
?s schema:name ?name .
}"
let $constructed := sem:sparql($query, (), (), $store)
return
<result>
<tripleCount>{count($constructed)}</tripleCount>
<samples>{
for $t in subsequence($constructed, 1, 3)
return <triple>{sem:triple-subject($t)} {sem:triple-predicate($t)} {sem:triple-object($t)}</triple>
}</samples>
</result>
'use strict';
const sem = require('/MarkLogic/semantics.xqy');
const seedDocs = cts.search(cts.collectionQuery('wild-llamas')).toArray().slice(0, 300);
const seedTriples = [];
seedDocs.forEach((docNode) => {
const id = fn.normalizeSpace(fn.string(docNode.xpath('id')));
const name = fn.normalizeSpace(fn.string(docNode.xpath('name')));
if (id && name) {
seedTriples.push(sem.triple(
sem.iri(`https://cleverllamas.com/llamaverse/llama/${id}`),
sem.iri('http://schema.org/name'),
name
));
}
});
const store = sem.inMemoryStore(seedTriples);
const query =
'PREFIX schema: <http://schema.org/>\n' +
'CONSTRUCT {\n' +
' ?s schema:label ?name .\n' +
'}\n' +
'WHERE {\n' +
' ?s schema:name ?name .\n' +
'}';
const resultTriples = sem.sparql(query, null, null, store).toArray();
const results = {
seedRowCount: seedTriples.length,
tripleCount: resultTriples.length,
samples: resultTriples.slice(0, 3).map((triple) => ({
subject: sem.tripleSubject(triple),
predicate: sem.triplePredicate(triple),
object: sem.tripleObject(triple)
}))
};
({
sample: 'sem/semantics-and-sparql-starter-pack/assets/sem-sparql-construct.sjs',
kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
count: Array.isArray(results) ? results.length : 0,
data: results
});
X-Path: /result
<result><tripleCount>194</tripleCount><samples><triple>https://cleverllamas.com/llamaverse/llama/00cf82a3-4937-40c6-80fe-6ae0f1100364 http://schema.org/label Bonnie</triple><triple>https://cleverllamas.com/llamaverse/llama/0300e2ed-06db-427d-9435-b162fa47c9e6 http://schema.org/label Erica</triple><triple>https://cleverllamas.com/llamaverse/llama/04ea3126-3402-4ea7-aae7-7d5bd36e1224 http://schema.org/label Susan</triple></samples></result>
| Field | Value |
|---|---|
| XPath | /result |
| Triple count | 3 |
| Sample 1 | Bonnie → schema:label → Bonnie |
| Sample 2 | Erica → schema:label → Erica |
| Sample 3 | Susan → schema:label → Susan |
What to notice: CONSTRUCT gives you an output graph that can be reused in downstream Semantics processing, not just displayed as rows. The same sem:sparql options table above applies here. A SELECT returns bindings; a CONSTRUCT returns triples, so the result handling code must differ.
sem:graph-delete and graph verification
Use this pattern when you need explicit lifecycle handling for named graphs in scripts and operations workflows.
Source docs: https://docs.marklogic.com/11.0/sem:graph-delete
| Option / Argument | What it controls | Used here |
|---|---|---|
sem:sparql($query) | Reads the target graph for an operational count | Use a separate read query when post-commit verification is required |
sem:graph-delete($graph) | Deletes all triples in the target graph | Applied to the same named graph URI |
xquery version "1.0-ml";
import module namespace sem = "http://marklogic.com/semantics"
at "/MarkLogic/semantics.xqy";
let $graph := sem:iri("https://cleverllamas.com/llamaverse/graphs/wild-llamas")
let $deleted := sem:graph-delete($graph)
let $triples :=
for $doc in cts:search(collection("wild-llamas"), cts:true-query())[1 to 300]
let $id := fn:normalize-space(fn:string($doc/id))
let $name := fn:normalize-space(fn:string($doc/name))
let $home := fn:normalize-space(fn:string($doc/placeOfBirth))
where $id ne "" and $name ne "" and $home ne ""
return (
sem:triple(sem:iri("https://cleverllamas.com/llamaverse/llama/" || $id), sem:iri("http://schema.org/name"), $name),
sem:triple(sem:iri("https://cleverllamas.com/llamaverse/llama/" || $id), sem:iri("http://schema.org/homeLocation"), $home)
)
let $inserted := sem:graph-insert($graph, $triples)
return
<result>
<graph>{$graph}</graph>
<deleteReturnCount>{count($deleted)}</deleteReturnCount>
<insertedTripleCount>{count($triples)}</insertedTripleCount>
</result>
'use strict';
const sem = require('/MarkLogic/semantics.xqy');
declareUpdate();
const graphIri = 'https://cleverllamas.com/llamaverse/graphs/wild-llamas';
sem.graphDelete(graphIri);
const seedDocs = cts.search(cts.collectionQuery('wild-llamas')).toArray().slice(0, 300);
const reloadedTriples = [];
seedDocs.forEach((docNode) => {
const id = fn.normalizeSpace(fn.string(docNode.xpath('id')));
const name = fn.normalizeSpace(fn.string(docNode.xpath('name')));
const home = fn.normalizeSpace(fn.string(docNode.xpath('placeOfBirth')));
if (id && name && home) {
const subject = sem.iri(`https://cleverllamas.com/llamaverse/llama/${id}`);
reloadedTriples.push(
sem.triple(subject, sem.iri('http://schema.org/name'), name),
sem.triple(subject, sem.iri('http://schema.org/homeLocation'), home)
);
}
});
const inserted = sem.graphInsert(sem.iri(graphIri), reloadedTriples);
const results = {
graph: graphIri,
deleted: true,
seedRowCount: reloadedTriples.length / 2,
insertedTripleCount: reloadedTriples.length,
insertedDocumentCount: inserted.length,
sample: reloadedTriples.slice(0, 1).map((triple) => ({
subject: sem.tripleSubject(triple),
predicate: sem.triplePredicate(triple),
object: sem.tripleObject(triple)
}))
};
({
sample: 'sem/semantics-and-sparql-starter-pack/assets/sem-graph-lifecycle.sjs',
kind: Array.isArray(results) ? (results.every((item) => typeof item === 'object' && 'subject' in item && 'predicate' in item && 'object' in item) ? 'triples' : 'rows') : ((results !== null && typeof results === 'object') ? 'object' : 'scalar'),
count: Array.isArray(results) ? results.length : 0,
data: results
});
graph: https://cleverllamas.com/llamaverse/graphs/wild-llamas
deleteReturnCount: 0
insertedTripleCount: 388
| Field | Value |
|---|---|
| Delete return count | 0 |
| Inserted triple count | 388 |
What to notice: the asset performs both updates and reports the actual insert result. sem:graph-delete returns an empty sequence on this run — read that as "no news," not "nothing happened." Query the graph in a separate read transaction when you need post-commit verification.
Graph Queries Open New Worlds
Decision rule: scope store inputs explicitly before tuning SPARQL options; dataset boundaries drive result trust more than query cleverness.
Once you're thinking in triples and SPARQL, these Optic patterns become even more useful:
- Data Source Starter Pack —
op:fromSparql()lets you feed SPARQL row sets into larger Optic plans. - Relevance and Scoring Starter Pack — SPARQL results can also be ranked and scored for relevance-based reporting.
Need Some Help?
Looking for more information on this subject or any other topic related to MarkLogic? Contact Us (info@cleverllamas.com) to find out how we can assist you with consulting or training!
- Sample Data and Execution Context
- API Context for This Pack
- Functions in This Pack
- sem:rdf-parse + sem:graph-insert
- sem:sparql
- sem:sparql with external bindings
- sem:sparql (CONSTRUCT)
- sem:graph-delete and graph verification
- Graph Queries Open New Worlds