Optic Data Source Starter Pack

Build Optic plans from views, lexicons, search, documents, and descriptors

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

Each source is shown with its plan and result together so you can see the output shape before you commit to a design.

The goal is not to memorise every accessor. The goal is to know which source shape you should start from and why.

Pick the wrong source shape and the maintenance bill will find you later.

The examples in this article assume the llamaverse (v2.0+) is deployed. The llamaverse sample data is freely available from github.com/cleverllamas/llamaverse - see the llamaverse article for full setup instructions.

Language and Intent

Like the cts and xdmp starter packs, these examples are shown in both language tabs for consistency across the API family. All examples shown are read-only except two sources that support write operations:

  • op:fromDocDescriptors() — use when you are shaping documents or operational reports to write. The example shows reading from a seed document and extracting data; in real workflows, you would pipe the result to .write() or pass it to an update operation.
  • op:fromParam() — use when parameter input drives an insert or update flow. The example shows a realistic update context where incoming parameters feed directly into document insert operations.

All other sources in this pack are query/read only.

The code assets in this pack are validated against the local MarkLogic 11 runtime used by this repository.

Source Map

FunctionWhat it gives youUse whenSample data
op.fromView()Rows from a TDE or relational viewYour data is already modeled as columnsllamaverse.wildLlamas, llamaverse.secretPowers
op.fromLexicons()Indexed values and fragment-linked rowsYou need lexicon-backed rows for joins or reportingBreed plus an indexed numeric/path value such as heightCm
op.fromSearchDocs()Document-shaped search hitsYou want full-text search without building a view firstWild llama documents matched by text
op.fromSearch()Search rows with scoreYou need ranked search and downstream joinscts.wordQuery("sung", "stemmed")
op.fromSql()Rows produced from SQL text over Optic viewsYou want SQL syntax over the same row sourcesllamaverse.llamas query via SQL SELECT
op.fromSparql()Rows produced from SPARQL SELECT and VALUESYou want explicit row construction or SPARQL-driven shapingInline VALUES rows and CSV-seeded VALUES rows
op.fromDocUris()URI-backed rows with fragment referencesYou want a URI-driven candidate setWild-llama documents in a collection
op.fromDocDescriptors()Document-descriptor rows for write/report flowsYou are shaping documents or operational reportsForest-status report rows
op.fromLiterals()Synthetic rows with fixed literal valuesYou need a self-contained rowset for demos or joinsSmall hard-coded rows, usually not llamaverse-backed
op.fromParam()Rows derived from parametersYou need an input-shaped rowset for update or pipeline plumbingApplication parameters or update payload inputs

op.fromView()

op.fromView() is the data source you reach for when the source model is already tabular. In llamaverse, that means the TDE view has already turned document content into rows and columns. The example below uses the llamaverse.wildLlamas view so the article stays grounded in a real view source rather than a synthetic one.

Option / ArgumentWhat it controlsUsed here
schemaSchema containing the viewllamaverse
viewView name within the schemallamas
qualifier (optional)Alias for column qualificationShown in the advanced example below
systemCols (optional)System columns such as fragment idShown in the advanced example below
{
  "envelope": {
    "instance": {
      "llamas": {
        "id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
        "name": "Aaron",
        "breed": "Huacaya",
        "placeOfBirth": "Cusco, Peru"
      }
    }
  }
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";

op:from-view("llamaverse", "llamas")
  => op:select(("name", "breed", "placeOfBirth"))
  => op:order-by(op:asc("name"))
  => op:limit(3)
  => op:result()
'use strict';

const op = require('/MarkLogic/optic');

const results = op.fromView('llamaverse', 'llamas')
  .select(['id', 'name', 'breed'])
  .orderBy(op.asc('name'))
  .limit(10)
  .result();

({
  sample: 'optic/data-source-starter-pack/assets/op-from-view.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
});
{"llamaverse.llamas.name": "Aaron", "llamaverse.llamas.breed": "Huacaya", "llamaverse.llamas.placeOfBirth": "Cusco, Peru"}
{"llamaverse.llamas.name": "Angela", "llamaverse.llamas.breed": "Huacaya", "llamaverse.llamas.placeOfBirth": "Cusco, Peru"}
{"llamaverse.llamas.name": "Anthony", "llamaverse.llamas.breed": "Huacaya", "llamaverse.llamas.placeOfBirth": "Santiago, Chile"}
llamaverse.llamas.namellamaverse.llamas.breedllamaverse.llamas.placeOfBirth
AaronHuacayaCusco, Peru
AngelaHuacayaCusco, Peru
AnthonyHuacayaSantiago, Chile

What to notice: the source is not raw JSON anymore. It is a view row set, which makes downstream joins and projections much easier to reason about.

Llamaverse context: this is the right pattern when the relevant data already lives in a named view such as llamaverse.wildLlamas or llamaverse.secretPowers.

op.fromView() with qualifier and system columns

Use this variant when you want stable, explicit column qualification and system-level traceability (for example fragment IDs) in downstream joins and diagnostics.

Option / ArgumentWhat it controlsUsed here
qualifierPrefixes columns from this source"l"
systemColsAdds system-level columns to the rowsetop:fragment-id-col("fragmentId")
{
  "name": "Aaron",
  "breed": "Huacaya",
  "placeOfBirth": "Cusco, Peru",
  "secretPowerId": "d8839ba6-2b77-4bcc-9927-b86cdfecb9fb"
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";

op:from-view("llamaverse", "llamas", "l", op:fragment-id-col("fragmentId"))
  => op:select(("name", "breed", "fragmentId"))
  => op:order-by(op:asc("name"))
  => op:limit(3)
  => op:result()
'use strict';

const op = require('/MarkLogic/optic');

const results = op.fromView('llamaverse', 'llamas')
  .select([
    op.col('name'),
    op.fragmentIdCol('fragmentId')
  ])
  .orderBy(op.asc('fragmentId'))
  .limit(10)
  .result();

({
  sample: 'optic/data-source-starter-pack/assets/op-from-view-qualified-system-cols.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
});
{"l.name": "Aaron", "l.breed": "Huacaya", "l.fragmentId": "http://marklogic.com/fragment/00001D02623980AC"}
{"l.name": "Angela", "l.breed": "Huacaya", "l.fragmentId": "http://marklogic.com/fragment/00000772623980AC"}
{"l.name": "Anthony", "l.breed": "Huacaya", "l.fragmentId": "http://marklogic.com/fragment/00000B32623980AC"}
l.namel.breedl.fragmentId
AaronHuacayahttp://marklogic.com/fragment/00001D02623980AC
AngelaHuacayahttp://marklogic.com/fragment/00000772623980AC
AnthonyHuacayahttp://marklogic.com/fragment/00000B32623980AC

What to notice: qualifier naming is a practical tuning and maintainability choice. It keeps later join logic explicit, and it prevents ambiguous-column errors as plans grow.

op.fromLexicons()

op.fromLexicons() is the right choice when you want indexed values rather than document text or view rows. It is especially useful for audit-style reports and join-heavy plans where the indexed value itself is the interesting starting point. In this runtime, the clean validated example uses the breed value together with indexed heightCm rows.

Option / ArgumentWhat it controlsUsed here
lexicons mapWhich lexicon-backed columns are projectedbreed and heightCm references
query (optional)Filter query before row materializationEmpty sequence in this sample
options (optional)Planner options and system columnsop:fragment-id-col("fragmentId")
{
  "envelope": {
    "instance": {
      "llamas": {
        "breed": "Huacaya",
        "heightCm": 138
      }
    }
  }
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";

op:from-lexicons(
  map:entry("breed", cts:element-reference(xs:QName("breed")))
    => map:with("heightCm", cts:path-reference("/heightCm", ("type=int"))),
  (),
  op:fragment-id-col("fragmentId")
)
  => op:where(op:eq(op:col("breed"), "Suri"))
  => op:select(("fragmentId", "breed", "heightCm"))
  => op:limit(3)
  => op:result()
'use strict';

const op = require('/MarkLogic/optic');

const cityRef = cts.elementReference(fn.QName('', 'birthplaceCity'));

const results = op.fromLexicons(
  [
    op.col('city', cityRef),
    op.fragmentIdCol('fragmentId')
  ]
)
  .where(op.eq(op.col('city'), 'Cusco'))
  .select(['city', 'fragmentId'])
  .limit(10)
  .result();

({
  sample: 'optic/data-source-starter-pack/assets/op-from-lexicons.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
});
{"fragmentId": "http://marklogic.com/fragment/000050B2623980AC", "breed": "Suri", "heightCm": 138}
{"fragmentId": "http://marklogic.com/fragment/00005242623980AC", "breed": "Suri", "heightCm": 186}
{"fragmentId": "http://marklogic.com/fragment/00005302623980AC", "breed": "Suri", "heightCm": 172}
fragmentIdbreedheightCm
http://marklogic.com/fragment/000050B2623980ACSuri138
http://marklogic.com/fragment/00005242623980ACSuri186
http://marklogic.com/fragment/00005302623980ACSuri172

What to notice: lexicons are indexed data, so the plan starts with the value you want to reason about, not the document you happen to read it from. The fragment id stays available, which is what makes this source useful for later joins.

Llamaverse context: this is the better fit when the question is about indexed values that already exist in the dataset, such as breed plus indexed height values, rather than the full document payload.

op.fromSearchDocs()

op.fromSearchDocs() is the Optic equivalent of cts.search(). Use it when you want document-shaped search results without first projecting the data into a view. The llamaverse example below searches the wild-llamas collection directly, so the reader can see how the search source behaves before any view modeling gets involved.

Option / ArgumentWhat it controlsUsed here
querySearch predicate used to produce matchescts:and-query(...)
qualifier (optional)Alias for produced columnsNot used in this sample
{
  "envelope": {
    "instance": {
      "llamas": {
        "name": "Kyle",
        "description": "Kyle is a gray-eyed llama ... sung for a group of prestigious penguins ..."
      }
    }
  }
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";

op:from-search-docs(
  cts:and-query((
    cts:collection-query("wild-llamas"),
    cts:word-query("sung", "stemmed")
  ))
)
  => op:order-by((op:desc("score"), op:asc("uri")))
  => op:limit(3)
  => op:result()
'use strict';

const op = require('/MarkLogic/optic');

const query = cts.andQuery([
  cts.collectionQuery('llamaverse'),
  cts.wordQuery('llama')
]);

const results = op.fromSearchDocs(query)
  .orderBy(op.desc('score'))
  .limit(10)
  .result();

({
  sample: 'optic/data-source-starter-pack/assets/op-from-search-docs.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
});
{"doc": {"id": "00048384-cb13-4557-805e-a6b4e57f7eab", "name": "Madison", "heightCm": 145, "weightKg": 141, "eyeColor": "Gray", "hairColor": "Silver", "breed": "Hybrid", "placeOfBirth": "Parkerville, Ukraine", "description": "Madison is a gray-eyed llama with silver hair, standing 145 cm tall. Originally from Parkerville, Ukraine, Madison enjoys gardening, knitting, singing. Known for their curious and playful personality, Madison is a beloved member of the llama community.", "classificationSpeciesKey": "Lama_glama_andinus"}, "score": 56576, "uri": "/cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json"}
{"doc": {"id": "0005e261-4302-4ae8-9574-732a54040423", "name": "Ricky", "heightCm": 157, "weightKg": 136, "eyeColor": "Gray", "hairColor": "Gray", "breed": "Suri", "placeOfBirth": "Josephton, Bahrain", "description": "Ricky is a gray-eyed llama with gray hair, standing 157 cm tall. Originally from Josephton, Bahrain, Ricky enjoys dancing, singing, knitting. Known for their energetic and gentle personality, Ricky is a beloved member of the llama community.", "classificationSpeciesKey": "Lama_glama_andinus"}, "score": 56576, "uri": "/cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json"}
{"doc": {"id": "0a6911e5-0d17-44e1-a114-cb747490f469", "name": "Kyle", "heightCm": 175, "weightKg": 185, "eyeColor": "Gray", "hairColor": "Gray", "breed": "Huacaya", "placeOfBirth": "Lynnmouth, Myanmar", "description": "Kyle is a gray-eyed llama with gray hair, standing 175 cm tall. Originally from Lynnmouth, Myanmar where he once sung for a group of prestegious penguins, knitting, hiking. Known for their playful and energetic personality, Kyle is a beloved member of the llama community.", "relatedTo": {"id": "96c897e6-dd56-40aa-af5d-27b0cd6bf7e0", "relationship": "15th cousin 9th removed"}, "classificationSpeciesKey": "Lama_glama_silvestris"}, "score": 62208, "uri": "/cleverllamas/llamaverse/raw/wild-llamas/llamas/0a6911e5-0d17-44e1-a114-cb747490f469.json"}
docscoreuri
[object Object]56576/cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json
[object Object]56576/cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json
[object Object]62208/cleverllamas/llamaverse/raw/wild-llamas/llamas/0a6911e5-0d17-44e1-a114-cb747490f469.json

What to notice: this is the document-shaped path. The live result includes the matched document payload plus the URI and score, which keeps you in search space without forcing you to introduce a view first.

Llamaverse context: this is a raw-document path over the wild-llamas collection, which makes it useful when you want to start from the source documents themselves.

op.fromSearch()

op.fromSearch() is what you use when the score matters. It starts from the same kind of search predicate, but it is the source you want when ranking is part of the story. Here the plan still begins with wild-llamas, and the output keeps the score values visible as the main reporting surface.

Option / ArgumentWhat it controlsUsed here
querySearch predicate used to produce matchescts:and-query(...)
columnsExtra columns emitted by the source("fragmentId", "score")
options (optional)Execution optionsNot used in this sample
{
  "query": "cts.wordQuery('sung', 'stemmed')",
  "source": "wild-llamas",
  "intent": "ranked search"
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";

op:from-search(
  cts:and-query((
    cts:collection-query("wild-llamas"),
    cts:word-query("sung", "stemmed")
  )),
  ("fragmentId", "score")
)
  => op:limit(3)
  => op:result()
'use strict';

const op = require('/MarkLogic/optic');

const query = cts.andQuery([
  cts.collectionQuery('llamaverse'),
  cts.wordQuery('llama')
]);

const results = op.fromSearch(query)
  .limit(10)
  .result();

({
  sample: 'optic/data-source-starter-pack/assets/op-from-search.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
});
{"score": 56576}
{"score": 56576}
{"score": 56576}
score
56576
56576
56576

What to notice: op.fromSearch() is the source that makes ranking explicit. If the score is not part of the answer, this is probably the wrong starting point.

Configuration note: because this source often starts from cts:word-query patterns, wildcard and linguistic behaviour still depend on database word-search settings. See Word Search Configuration Settings That Affect These Samples.

Llamaverse context: use this when you want the same wild-llamas search but need to keep the score column available for ordering, highlighting, or explanation.

op.fromSql()

op.fromSql() is useful when you want SQL syntax but still execute over the same Optic view data in MarkLogic. It is a practical bridge for teams that think in SQL first while still staying in Optic plans.

Option / ArgumentWhat it controlsUsed here
sqlSQL text to compile into an Optic planselect name, breed, placeOfBirth ...
options (optional)Compilation optionsNot used in this sample
{
  "name": "Aaron",
  "breed": "Huacaya",
  "placeOfBirth": "Cusco, Peru",
  "secretPowerId": "d8839ba6-2b77-4bcc-9927-b86cdfecb9fb"
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";

op:from-sql("select name, breed, placeOfBirth from llamaverse.llamas order by name limit 3")
  => op:result()
'use strict';

const op = require('/MarkLogic/optic');

const results = op.fromSQL('SELECT 1 AS id, \"llama\" AS species')
  .result();

({
  sample: 'optic/data-source-starter-pack/assets/op-from-sql.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
});
{"llamaverse.llamas.name": "Aaron", "llamaverse.llamas.breed": "Huacaya", "llamaverse.llamas.placeOfBirth": "Cusco, Peru"}
{"llamaverse.llamas.name": "Angela", "llamaverse.llamas.breed": "Huacaya", "llamaverse.llamas.placeOfBirth": "Cusco, Peru"}
{"llamaverse.llamas.name": "Anthony", "llamaverse.llamas.breed": "Huacaya", "llamaverse.llamas.placeOfBirth": "Santiago, Chile"}
llamaverse.llamas.namellamaverse.llamas.breedllamaverse.llamas.placeOfBirth
AaronHuacayaCusco, Peru
AngelaHuacayaCusco, Peru
AnthonyHuacayaSantiago, Chile

What to notice: this runs over the same llamaverse.llamas view as op.fromView(). The difference is query expression style, not underlying data access.

op.fromSparql()

op.fromSparql() is useful when you want to produce a rowset from SPARQL SELECT statements and explicit VALUES blocks. It is especially handy for synthetic or externally-seeded rows where you still want Optic downstream operations.

Option / ArgumentWhat it controlsUsed here
sparqlSPARQL text to compile into an Optic planSELECT + VALUES
qualifier (optional)Alias for row qualificationNot used in base sample
options (optional)Planner/runtime optionsNot used in base sample
PREFIX xs: <http://www.w3.org/2001/XMLSchema#>
SELECT * WHERE {
  VALUES (?name ?breed ?heightCm) {
    ("Aaron" "Huacaya" "138"^^xs:integer)
    ("Angela" "Huacaya" "136"^^xs:integer)
    ("Anthony" "Suri" "142"^^xs:integer)
  }
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";

let $sparql :=
'prefix xs: <http://www.w3.org/2001/XMLSchema#>
select * where {
  values (?name ?breed ?heightCm) {
    ("Aaron" "Huacaya" "138"^^xs:integer)
    ("Angela" "Huacaya" "136"^^xs:integer)
    ("Anthony" "Suri" "142"^^xs:integer)
  }
}'

return
  op:from-sparql($sparql)
  => op:order-by(op:asc("name"))
  => op:result()
'use strict';

const op = require('/MarkLogic/optic');

const results = op.fromSPARQL(`
  PREFIX schema: <http://schema.org/>
  SELECT ?name ?home
  WHERE {
    ?s schema:name ?name .
    ?s schema:home ?home .
  }
`)
  .orderBy(op.asc('name'))
  .result();

({
  sample: 'optic/data-source-starter-pack/assets/op-from-sparql-values.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", "breed": "Huacaya", "heightCm": 138}
{"name": "Angela", "breed": "Huacaya", "heightCm": 136}
{"name": "Anthony", "breed": "Suri", "heightCm": 142}
namebreedheightCm
AaronHuacaya138
AngelaHuacaya136
AnthonySuri142

Bonus: CSV Rows to SPARQL VALUES

This variant takes a small CSV payload, parses the rows, and constructs a SPARQL VALUES block dynamically before passing it to op.fromSparql().

name,breed,heightCm
Aaron,Huacaya,138
Angela,Huacaya,136
Anthony,Suri,142
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";

let $nl := codepoints-to-string(10)
let $csv :=
"name,breed,heightCm
Aaron,Huacaya,138
Angela,Huacaya,136
Anthony,Suri,142"

let $lines := fn:subsequence(fn:tokenize($csv, "\n"), 2)
let $rows :=
  for $line in $lines
  let $parts := fn:tokenize($line, ",")
  return
    '("' || $parts[1] || '" "' || $parts[2] || '" "' || $parts[3] || '"^^xs:integer)'

let $sparql :=
  string-join((
    "prefix xs: <http://www.w3.org/2001/XMLSchema#>",
    "select * where {",
    "  values (?name ?breed ?heightCm) {",
    "    " || string-join($rows, $nl || "    "),
    "  }",
    "}"
  ), $nl)

return
  op:from-sparql($sparql)
  => op:order-by(op:asc("name"))
  => op:result()
'use strict';

const op = require('/MarkLogic/optic');

const csv = 'id,name\n1,Aaron\n2,Angela\n3,Anthony';
const rows = fn.tokenize(csv, '\\n').toArray().slice(1).map((line) => {
  const cols = fn.tokenize(line, ',').toArray();
  return { id: Number(cols[0]), name: cols[1] };
});

const results = op.fromLiterals(rows)
  .orderBy(op.asc('id'))
  .result();

({
  sample: 'optic/data-source-starter-pack/assets/op-from-sparql-values-from-csv.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", "breed": "Huacaya", "heightCm": 138}
{"name": "Angela", "breed": "Huacaya", "heightCm": 136}
{"name": "Anthony", "breed": "Suri", "heightCm": 142}
namebreedheightCm
AaronHuacaya138
AngelaHuacaya136
AnthonySuri142

What to notice: this is a clean pattern for ingesting lightweight external tabular data into Optic without creating a persistent view first.

op.fromDocUris()

op.fromDocUris() is the source you use when the URI itself matters. It is useful for targeted updates, delete flows, and any plan where the candidate set begins as document identifiers instead of rows or search hits. For llamaverse, that means using the collection query to get actual wild-llama document URIs first.

Option / ArgumentWhat it controlsUsed here
queryWhich URIs are includedcts:collection-query("wild-llamas")
options (optional)Extra source optionsNot used in this sample
{
  "collection": "wild-llamas",
  "goal": "get a URI-backed candidate set"
}
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";

op:from-doc-uris(cts:collection-query("wild-llamas"))
  => op:limit(3)
  => op:result()
'use strict';

const op = require('/MarkLogic/optic');

const uris = cts.uris(null, ['limit=20'], cts.collectionQuery('llamaverse')).toArray();

const results = op.fromDocUris(uris)
  .limit(10)
  .result();

({
  sample: 'optic/data-source-starter-pack/assets/op-from-doc-uris.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
});
{"uri": "/cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json"}
{"uri": "/cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json"}
{"uri": "/cleverllamas/llamaverse/raw/wild-llamas/llamas/000eca10-d166-469f-b17a-3c3b35ee0883.json"}
uri
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/000eca10-d166-469f-b17a-3c3b35ee0883.json

What to notice: this gives you the join key first. That is often what you want before any destructive or write-oriented step.

Llamaverse context: when you need URI-level control over the wild-llamas collection, this is the cleanest source shape to start with.

op.fromDocDescriptors()

op.fromDocDescriptors() is the descriptor-oriented source in the Optic family. It is the right fit when you are shaping document payloads, validation inputs, or operational reports that need to be turned into documents later.

Option / ArgumentWhat it controlsUsed here
descriptorsDescriptor sequence containing URI/doc pairsGenerated from forest status XML
qualifier (optional)Alias for source columnsNot used in this sample

For this starter pack, the example builds a seed document containing xdmp:forest-status() data, splits that seed into descriptor rows, and then uses op.xpath() to extract a repeatable operational report table.

<all-forests>
  <forest-status>
    <forest-id>1</forest-id>
    <forest-name>Documents-1</forest-name>
    <state>online</state>
  </forest-status>
</all-forests>
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";
import module namespace ofn = "http://marklogic.com/optic/expression/fn" at "/MarkLogic/optic/optic-fn.xqy";

let $forestDoc := document {
  element all-forests {
    for $forest in subsequence(xdmp:forest-status(xdmp:forests()), 1, 3)
    return $forest
  }
}

let $descriptors :=
  for $forest at $position in $forestDoc/*:all-forests/*:forest-status
  return
    map:entry("uri", "/cleverllamas/llamaverse/scratch/reports/forest-status-" || $position || ".xml")
      => map:with("doc", document { $forest })

return
op:from-doc-descriptors($descriptors)
  => op:select((
       "uri",
       op:as("forestName", ofn:string(op:xpath("doc", "/*:forest-status/*:forest-name"))),
       op:as("availability", ofn:string(op:xpath("doc", "/*:forest-status/*:availability"))),
       op:as("state", ofn:string(op:xpath("doc", "/*:forest-status/*:state")))
     ))
  => op:result()
'use strict';

const op = require('/MarkLogic/optic');

const descriptors = xdmp.forests().toArray().map((forestId) =>
  ({
    uri: `/forest-status/${forestId}.xml`,
    doc: xdmp.forestStatus(forestId)
  })
);

const results = op.fromDocDescriptors(descriptors)
  .select([
    op.as('forestId', op.xpath('fn:string(/status/forest-id)')),
    op.as('forestName', op.xpath('fn:string(/status/forest-name)'))
  ])
  .result();

({
  sample: 'optic/data-source-starter-pack/assets/op-from-doc-descriptors.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
});
{"uri": "/cleverllamas/llamaverse/scratch/reports/forest-status-1.xml", "forestName": "cleverllamas-schemas-1", "availability": "online", "state": "open"}
{"uri": "/cleverllamas/llamaverse/scratch/reports/forest-status-2.xml", "forestName": "Triggers", "availability": "online", "state": "open"}
{"uri": "/cleverllamas/llamaverse/scratch/reports/forest-status-3.xml", "forestName": "Schemas", "availability": "online", "state": "open"}
uriforestNameavailabilitystate
/cleverllamas/llamaverse/scratch/reports/forest-status-1.xmlcleverllamas-schemas-1onlineopen
/cleverllamas/llamaverse/scratch/reports/forest-status-2.xmlTriggersonlineopen
/cleverllamas/llamaverse/scratch/reports/forest-status-3.xmlSchemasonlineopen

What to notice: this pattern shows how to turn a structured seed document into descriptor rows first and then into a clean rowset using op.xpath(). It is especially useful when operational state or complex document payloads need to become columns for downstream joins and filtering.

Llamaverse context: the example is intentionally not a llama document example. It uses xdmp:forest-status() to show that Optic data sources can carry operational state into a report plan, and that descriptors are the bridge between unstructured operational data and structured Optic rowsets.

op.fromLiterals()

op.fromLiterals() is the simplest way to create a small, explicit rowset when you need a self-contained example. It is not the main data source family for this pack, but it is still important because it lets you build repeatable examples without depending on a database object first.

Option / ArgumentWhat it controlsUsed here
rowsLiteral row valuesThree static llama rows
qualifier (optional)Alias for resulting columnsNot used in this sample
[
  { "name": "Aaron", "breed": "Huacaya" },
  { "name": "Anna", "breed": "Huacaya" },
  { "name": "Jason", "breed": "Huacaya" }
]
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";

op:from-literals((
  map:entry("name", "Aaron") => map:with("breed", "Huacaya"),
  map:entry("name", "Anna") => map:with("breed", "Huacaya"),
  map:entry("name", "Jason") => map:with("breed", "Huacaya")
))
  => op:select(("name", "breed"))
  => op:result()
'use strict';

const op = require('/MarkLogic/optic');

const rows = [
  { id: 1, species: 'llama' },
  { id: 2, species: 'alpaca' }
];

const results = op.fromLiterals(rows)
  .select(['id', 'species'])
  .result();

({
  sample: 'optic/data-source-starter-pack/assets/op-from-literals.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", "breed": "Huacaya"}
{"name": "Anna", "breed": "Huacaya"}
{"name": "Jason", "breed": "Huacaya"}
namebreed
AaronHuacaya
AnnaHuacaya
JasonHuacaya

What to notice: this is the right way to keep a rowset fully self-contained when the point of the article is the plan shape, not the backing database object.

Llamaverse context: use literals when you need a tiny, reproducible stand-in for llamaverse data, but do not want to depend on a particular collection or view just to teach the pattern.

op.fromParam()

op.fromParam() is the input-shaping builder for parameter-driven flows. Use it when a plan needs caller-supplied values before branching into an update or insert operation. In MarkLogic 11, the documented write-oriented pattern is to bind descriptor rows, call op:write(), and finish with op:execute().

Option / ArgumentWhat it controlsUsed here
nameBinding key used at execution time"bindingParam"
qualifier (optional)Alias for source columnsEmpty sequence in this sample
columnTypesExpected descriptor shapeop:doc-col-types()
[
  {
    "uri": "/cleverllamas/llamaverse/scratch/optic-update/llama-aaron.json",
    "doc": {
      "llama": {
        "name": "Aaron",
        "breed": "Huacaya"
      }
    },
    "collections": ["optic-execute-demo"]
  }
]
xquery version "1.0-ml";
import module namespace op = "http://marklogic.com/optic" at "/MarkLogic/optic.xqy";
declare option xdmp:update "true";

let $rows := (
  map:entry("uri", "/cleverllamas/llamaverse/scratch/optic-update/llama-aaron.json")
    => map:with("doc", object-node {
         "llama": object-node {
           "name": "Aaron",
           "breed": "Huacaya"
         }
       })
    => map:with("collections", ("optic-execute-demo"))
)

let $_ :=
  op:from-param("bindingParam", (), op:doc-col-types())
    => op:write()
    => op:execute(
         map:entry("bindingParam", $rows),
         ("trace=opticExecuteDemo", "optimize=1")
       )

return fn:doc("/cleverllamas/llamaverse/scratch/optic-update/llama-aaron.json")
'use strict';

const op = require('/MarkLogic/optic');

const plan = op.fromParam('rows', 'row', op.docColTypes()).select(['uri']);
const rows = [
  { uri: '/clever-llamas/test/op-from-param/one.json' },
  { uri: '/clever-llamas/test/op-from-param/two.json' }
];

const writeResult = op.write(plan, rows).execute();

const results = {
  writeResult,
  docs: rows.map((row) => ({ uri: row.uri, exists: fn.exists(fn.doc(row.uri)) }))
};

({
  sample: 'optic/data-source-starter-pack/assets/op-from-param.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-URI: /cleverllamas/llamaverse/scratch/optic-update/llama-aaron.json
{"llama":{"name":"Aaron", "breed":"Huacaya"}}
LineValue
1X-URI: /cleverllamas/llamaverse/scratch/optic-update/llama-aaron.json
2{"llama":{"name":"Aaron", "breed":"Huacaya"}}

What to notice: op.fromParam() is the real-world pattern for update flows. The parameters come from a caller (REST endpoint, Data Services, or another application layer), flow through the Optic plan, and feed into document mutations. In this live-tested sample, the script reads the document back immediately after execution so the side effect is visible.

Supporting Sources

op.fromLiterals() and op.fromParam() are still useful, but they are supporting plan builders rather than the primary data-source family. If you need synthetic rows, constants, or update payload plumbing, keep them close to the Optic update material rather than mixing them into the main source taxonomy.

Sources are Just the Starting Line

Decision rule: pick source type by operational intent first (search, view, lexicon, param), then layer joins and analysis.

Once you've chosen your source, the real work begins:

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!