Optic Data Source Starter Pack
Build Optic plans from views, lexicons, search, documents, and descriptors
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
| Function | What it gives you | Use when | Sample data |
|---|---|---|---|
op.fromView() | Rows from a TDE or relational view | Your data is already modeled as columns | llamaverse.wildLlamas, llamaverse.secretPowers |
op.fromLexicons() | Indexed values and fragment-linked rows | You need lexicon-backed rows for joins or reporting | Breed plus an indexed numeric/path value such as heightCm |
op.fromSearchDocs() | Document-shaped search hits | You want full-text search without building a view first | Wild llama documents matched by text |
op.fromSearch() | Search rows with score | You need ranked search and downstream joins | cts.wordQuery("sung", "stemmed") |
op.fromSql() | Rows produced from SQL text over Optic views | You want SQL syntax over the same row sources | llamaverse.llamas query via SQL SELECT |
op.fromSparql() | Rows produced from SPARQL SELECT and VALUES | You want explicit row construction or SPARQL-driven shaping | Inline VALUES rows and CSV-seeded VALUES rows |
op.fromDocUris() | URI-backed rows with fragment references | You want a URI-driven candidate set | Wild-llama documents in a collection |
op.fromDocDescriptors() | Document-descriptor rows for write/report flows | You are shaping documents or operational reports | Forest-status report rows |
op.fromLiterals() | Synthetic rows with fixed literal values | You need a self-contained rowset for demos or joins | Small hard-coded rows, usually not llamaverse-backed |
op.fromParam() | Rows derived from parameters | You need an input-shaped rowset for update or pipeline plumbing | Application 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 / Argument | What it controls | Used here |
|---|---|---|
schema | Schema containing the view | llamaverse |
view | View name within the schema | llamas |
qualifier (optional) | Alias for column qualification | Shown in the advanced example below |
systemCols (optional) | System columns such as fragment id | Shown 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.name | llamaverse.llamas.breed | llamaverse.llamas.placeOfBirth |
|---|---|---|
| Aaron | Huacaya | Cusco, Peru |
| Angela | Huacaya | Cusco, Peru |
| Anthony | Huacaya | Santiago, 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 / Argument | What it controls | Used here |
|---|---|---|
qualifier | Prefixes columns from this source | "l" |
systemCols | Adds system-level columns to the rowset | op: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.name | l.breed | l.fragmentId |
|---|---|---|
| Aaron | Huacaya | http://marklogic.com/fragment/00001D02623980AC |
| Angela | Huacaya | http://marklogic.com/fragment/00000772623980AC |
| Anthony | Huacaya | http://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 / Argument | What it controls | Used here |
|---|---|---|
lexicons map | Which lexicon-backed columns are projected | breed and heightCm references |
query (optional) | Filter query before row materialization | Empty sequence in this sample |
options (optional) | Planner options and system columns | op: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}
| fragmentId | breed | heightCm |
|---|---|---|
| http://marklogic.com/fragment/000050B2623980AC | Suri | 138 |
| http://marklogic.com/fragment/00005242623980AC | Suri | 186 |
| http://marklogic.com/fragment/00005302623980AC | Suri | 172 |
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 / Argument | What it controls | Used here |
|---|---|---|
query | Search predicate used to produce matches | cts:and-query(...) |
qualifier (optional) | Alias for produced columns | Not 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"}
| doc | score | uri |
|---|---|---|
| [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 / Argument | What it controls | Used here |
|---|---|---|
query | Search predicate used to produce matches | cts:and-query(...) |
columns | Extra columns emitted by the source | ("fragmentId", "score") |
options (optional) | Execution options | Not 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 / Argument | What it controls | Used here |
|---|---|---|
sql | SQL text to compile into an Optic plan | select name, breed, placeOfBirth ... |
options (optional) | Compilation options | Not 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.name | llamaverse.llamas.breed | llamaverse.llamas.placeOfBirth |
|---|---|---|
| Aaron | Huacaya | Cusco, Peru |
| Angela | Huacaya | Cusco, Peru |
| Anthony | Huacaya | Santiago, 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 / Argument | What it controls | Used here |
|---|---|---|
sparql | SPARQL text to compile into an Optic plan | SELECT + VALUES |
qualifier (optional) | Alias for row qualification | Not used in base sample |
options (optional) | Planner/runtime options | Not 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}
| name | breed | heightCm |
|---|---|---|
| Aaron | Huacaya | 138 |
| Angela | Huacaya | 136 |
| Anthony | Suri | 142 |
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}
| name | breed | heightCm |
|---|---|---|
| Aaron | Huacaya | 138 |
| Angela | Huacaya | 136 |
| Anthony | Suri | 142 |
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 / Argument | What it controls | Used here |
|---|---|---|
query | Which URIs are included | cts:collection-query("wild-llamas") |
options (optional) | Extra source options | Not 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 / Argument | What it controls | Used here |
|---|---|---|
descriptors | Descriptor sequence containing URI/doc pairs | Generated from forest status XML |
qualifier (optional) | Alias for source columns | Not 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"}
| uri | forestName | availability | state |
|---|---|---|---|
/cleverllamas/llamaverse/scratch/reports/forest-status-1.xml | cleverllamas-schemas-1 | online | open |
/cleverllamas/llamaverse/scratch/reports/forest-status-2.xml | Triggers | online | open |
/cleverllamas/llamaverse/scratch/reports/forest-status-3.xml | Schemas | online | open |
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 / Argument | What it controls | Used here |
|---|---|---|
rows | Literal row values | Three static llama rows |
qualifier (optional) | Alias for resulting columns | Not 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"}
| name | breed |
|---|---|
| Aaron | Huacaya |
| Anna | Huacaya |
| Jason | Huacaya |
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 / Argument | What it controls | Used here |
|---|---|---|
name | Binding key used at execution time | "bindingParam" |
qualifier (optional) | Alias for source columns | Empty sequence in this sample |
columnTypes | Expected descriptor shape | op: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"}}
| Line | Value |
|---|---|
| 1 | X-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:
- Optic Joins Starter Pack — Bring multiple sources together to answer questions that no single source can answer alone.
- Optic Data Manipulation Starter Pack — Transform and reshape the rows you've sourced.
- Optic Data Analysis Starter Pack — Aggregate, group, and summarise your row sets.
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!
- Language and Intent
- Source Map
- op.fromView()
- op.fromView() with qualifier and system columns
- op.fromLexicons()
- op.fromSearchDocs()
- op.fromSearch()
- op.fromSql()
- op.fromSparql()
- Bonus: CSV Rows to SPARQL VALUES
- op.fromDocUris()
- op.fromDocDescriptors()
- op.fromLiterals()
- op.fromParam()
- Supporting Sources
- Sources are Just the Starting Line