Semantics and SPARQL Starter Pack

Parse RDF, load graph data, and query with SPARQL in one practical reference

personClever Llamas
CleverLlamasMinimum Llamaverse Version: 2.5.0
databaseMinimum MarkLogic Version: 11

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.

ContextValue
Live sourcewild-llamas profile documents queried at runtime
Named graphhttps://cleverllamas.com/llamaverse/graphs/wild-llamas
Persistent API familysem:graph-insert, sem:graph-delete
Query API familysem: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 ItemWhat this pack uses
Data sourcewild-llamas profile documents and a captured RDF snapshot
Graph strategyNamed graph URI (https://cleverllamas.com/llamaverse/graphs/wild-llamas)
Primary functionssem:rdf-parse, sem:graph-insert, sem:sparql, sem:store, sem:graph-delete
Output styleCounts 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 / ArgumentWhat it controlsUsed here
sem:rdf-parse($text, $format)Parses RDF source text into triplesTurtle text parsed with "turtle" format
sem:graph-insert($graph, $triples)Stores triples in the target named graphGraph 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, '" .'
  )
), "&#10;")
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>
FieldValue
graphhttps://cleverllamas.com/llamaverse/graphs/wild-llamas
seedRowCount3
tripleCount6
sample triple 1Lisa → name → Lisa
sample triple 2Lisa → homeLocation → Kimberlychester, British Indian Ocean Territory (Chagos Archipelago)
sample triple 3David → 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-insert sample above.

Source docs: https://docs.marklogic.com/11.0/sem:sparql

Option / ArgumentWhat it controlsUsed here
$sparqlSPARQL query text to executeSELECT query over the named graph
$bindingsExternal variable bindings for query variablesNot set in this sample
$optionsSPARQL execution options (base, dataset options, parse/prepare/optimise controls)Not set in this sample (defaults apply)
$storeExplicit store source (for example sem:store(...) or sem:in-memory-store(...))In-memory store from parsed triples in JavaScript sample
$options ValueWhat it controlsDefault when omitted
"base=IRI"Base IRI used for relative IRIs in the queryNo explicit base override
"default-graph=IRI*"Adds one or more named graphs to the query default graphNo extra default graph additions
"named-graph=IRI*"Adds one or more named graphs to available named graph setNo extra named graph additions
"parse-check"Parse only; skip static checks and executionfalse
"prepare"Parse and optimise; do not executefalse
`"optimize=012"`
"trace=ID"Logs query plan/optimization/execution details using identifier IDNot 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"}
namehome
AaronPort Jacobstad, Zimbabwe
AlejandroNorth Joseph, Reunion
AlexanderEast 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 / ArgumentWhat it controlsUsed here
$bindingsExternal variable values injected into the SPARQL evaluationhomeFilter derived from seeded llamaverse rows
In-memory store seed stepData loaded before executing the binding querywild-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
FieldValue
bindingHomeA home value selected from the live llamaverse run
seedRowCount50
seedTripleCount100 (the JavaScript run also selects 50 valid rows)
rowCount1
Sample rowOne 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 / ArgumentWhat it controlsUsed here
$sparqlSPARQL query text to executeCONSTRUCT query producing triples
$bindingsExternal variable bindings for query variablesNot set in this sample
$optionsSPARQL execution options (base, dataset options, parse/prepare/optimise controls)Not set in this sample (defaults apply)
$storeExplicit 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>
FieldValue
XPath/result
Triple count3
Sample 1Bonnie → schema:label → Bonnie
Sample 2Erica → schema:label → Erica
Sample 3Susan → 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 / ArgumentWhat it controlsUsed here
sem:sparql($query)Reads the target graph for an operational countUse a separate read query when post-commit verification is required
sem:graph-delete($graph)Deletes all triples in the target graphApplied 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
FieldValue
Delete return count0
Inserted triple count388

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:

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!