Text Query Building Starter Pack
Compose text queries without turning your code into spaghetti
Each example keeps data, query, and result together so you can compare option behaviour without losing the thread.
This pack covers the cts text-building blocks you reach for when stakeholders say, "Can we search for this, but not that, and maybe also this other thing?"
When a query supports behaviour-changing options, the example below calls out the ones that matter and keeps the result tab focused on the output that explains the match.
The examples in this article assume the llamaverse (v2.4.0+) is deployed. The llamaverse sample data is freely available from github.com/cleverllamas/llamaverse - see the llamaverse article for full setup instructions. The query samples below use the Aaron llama document from that dataset so the input data, query, and result stay easy to compare as you review each example.
API Context for This Pack
| Context Item | What this pack uses |
|---|---|
| Data set | llamaverse v2.4.0+ sample data |
| Primary document scope | wild llama JSON documents (/cleverllamas/llamaverse/raw/wild-llamas/llamas/{uuid}.json) |
| Primary constraints | text terms with boolean composition (word, or, not) |
| Fields used | None in these examples |
| Index context | Word query resolution through cts text indexes, including stemming where requested |
If text matches look off, verify the exact query terms and options (for example stemming) before changing broader query logic.
Functions in This Pack
cts:word-query
The bread-and-butter text matcher. Simple, sharp, and surprisingly dangerous in the wrong hands.
Source docs: https://docs.marklogic.com/11.0/cts:word-query
| Option / Argument | What it controls | Used here |
|---|---|---|
$text | Word or phrase terms to match | Single and multi-word sample terms |
$options | Match behaviour (stemming, case, punctuation, synonym, wildcard) | See section below for the key options |
$weight | Relative relevance contribution for this query | Default weight in sample |
{
"envelope": {
"headers": { "type": "llamas" },
"instance": {
"llamas": {
"id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
"name": "Aaron",
"heightCm": 180,
"weightKg": 138,
"eyeColor": "Amber",
"hairColor": "Blonde",
"breed": "Huacaya",
"placeOfBirth": "Cusco, Peru",
"interests": ["playing chess", "birdwatching", "writing poetry"],
"medicalCondition": null,
"relatedTo": {
"id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
"relationship": "father"
},
"description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
"secretPower": {
"name": "Snake Charmer",
"id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
}
}
}
}
}
xquery version "1.0-ml";
let $query := cts:and-query((
cts:collection-query("wild-llamas"),
cts:word-query("llama")
))
return cts:uris((), (), $query)[1 to 20]
'use strict';
const query = cts.andQuery([
cts.collectionQuery('llamaverse'),
cts.wordQuery('llama')
]);
const results = cts.uris(null, ['limit=10'], query).toArray();
({
sample: 'cts/text-query-building-starter-pack/assets/cts-word-query.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
});
/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
/cleverllamas/llamaverse/raw/wild-llamas/llamas/002c7133-e985-4bd6-9ea8-f057fa0c6ee7.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48148c.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/007229e2-ee25-47c1-b18a-2e0c0ec0dbcd.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00863414-306e-49cb-acd0-6579bea5421e.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0087ad24-a720-4d82-b513-79ae5feebcc6.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/009cf2b9-d4db-4491-9cc2-34e2f43a5ee1.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00a0fe3a-da61-493c-990e-af7719db0d18.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00cf82a3-4937-40c6-80fe-6ae0f1100364.json
... (8 more URIs)
| # | URI |
|---|---|
| 1 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json |
| 2 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json |
| 3 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/000eca10-d166-469f-b17a-3c3b35ee0883.json |
| 4 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/002c7133-e985-4bd6-9ea8-f057fa0c6ee7.json |
| 5 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json |
| 6 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48148c.json |
| 7 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/007229e2-ee25-47c1-b18a-2e0c0ec0dbcd.json |
| 8 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/00863414-306e-49cb-acd0-6579bea5421e.json |
| 9 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/0087ad24-a720-4d82-b513-79ae5feebcc6.json |
| 10 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/009cf2b9-d4db-4491-9cc2-34e2f43a5ee1.json |
What to notice: this shows the baseline text match shape before layering additional boolean logic.
cts:word-query options to consider
cts:word-query() is not really a single shape of query. Its behaviour changes a lot once you start using options, and those options are often the difference between a useful search and a noisy one.
| Option / Argument | What it controls | Used here |
|---|---|---|
$options | Behaviour flags that tune matching and scoring | Detailed option families listed below |
$weight | Relative relevance impact of this word-query clause | Left at default in this pack's code samples |
$options Value | What it controls | Default behaviour |
|---|---|---|
"case-sensitive" / "case-insensitive" | Whether uppercase and lowercase are treated as different words | Derived from the text you pass in |
"diacritic-sensitive" / "diacritic-insensitive" | Whether accents and similar marks matter | Derived from the text you pass in |
"punctuation-sensitive" / "punctuation-insensitive" | Whether punctuation changes the match | Derived from the text you pass in |
"whitespace-sensitive" / "whitespace-insensitive" | Whether spacing affects the match | "whitespace-insensitive" |
"stemmed" / "unstemmed" | Whether morphological variants like plurals or verb forms are normalised | Depends on database configuration |
"wildcarded" / "unwildcarded" | Whether * and ? are treated as wildcard operators | Depends on database configuration and the text |
"synonym" | Whether equivalent terms are scored together | Off by default |
$weight | How strongly this query contributes to overall relevance | 1.0 |
For the llamaverse examples in this pack, the main practical choices are usually case-insensitive, stemmed, and synonym, because they make query intent clearer without forcing the reader to care about the exact surface form of the sample text.
cts:word-query documented option interactions
"wildcarded"and"unwildcarded"control whether wildcard operators are interpreted or treated literally."stemmed"and"unstemmed"control whether morphological variants are included."case-sensitive"/"case-insensitive"and"diacritic-sensitive"/"diacritic-insensitive"control lexical sensitivity."synonym"enables synonym-aware matching when synonym data is available.$weightcontrols the relative scoring contribution of the word-query clause.
cts:word-query case and diacritic sensitivity
Case and diacritic behaviour is where search often feels "random" to end users unless you are explicit about what the query should treat as equivalent.
| Option / Argument | What it controls | Used here |
|---|---|---|
"case-insensitive" / "case-sensitive" | Whether letter case changes matches | Both settings compared against the same collection scope |
"diacritic-insensitive" / "diacritic-sensitive" | Whether accent marks are normalised during matching | Both settings compared in a controlled mini-check |
{
"envelope": {
"headers": { "type": "llamas" },
"instance": {
"llamas": {
"id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
"name": "Aaron",
"heightCm": 180,
"weightKg": 138,
"eyeColor": "Amber",
"hairColor": "Blonde",
"breed": "Huacaya",
"placeOfBirth": "Cusco, Peru",
"interests": ["playing chess", "birdwatching", "writing poetry"],
"medicalCondition": null,
"relatedTo": {
"id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
"relationship": "father"
},
"description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
"secretPower": {
"name": "Snake Charmer",
"id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
}
}
}
}
}
xquery version "1.0-ml";
let $caseSample := text { "Aaron" }
let $caseInsensitive :=
cts:contains($caseSample, cts:word-query("aaron", ("case-insensitive")))
let $caseSensitive :=
cts:contains($caseSample, cts:word-query("aaron", ("case-sensitive")))
let $diacriticSample := text { "Peru Peru" }
let $diacriticInsensitive :=
cts:contains($diacriticSample, cts:word-query("Perú", ("diacritic-insensitive")))
let $diacriticSensitive :=
cts:contains($diacriticSample, cts:word-query("Perú", ("diacritic-sensitive")))
return
<result>
<caseInsensitiveMatches>{$caseInsensitive}</caseInsensitiveMatches>
<caseSensitiveMatches>{$caseSensitive}</caseSensitiveMatches>
<diacriticInsensitiveMatches>{$diacriticInsensitive}</diacriticInsensitiveMatches>
<diacriticSensitiveMatches>{$diacriticSensitive}</diacriticSensitiveMatches>
</result>
'use strict';
const text = 'Áaron likes LLAMAS';
const caseSensitive = cts.wordQuery('llamas', ['case-sensitive']);
const caseInsensitive = cts.wordQuery('llamas', ['case-insensitive']);
const diacriticSensitive = cts.wordQuery('aaron', ['diacritic-sensitive']);
const diacriticInsensitive = cts.wordQuery('aaron', ['diacritic-insensitive']);
const results = {
caseSensitive: cts.contains(text, caseSensitive),
caseInsensitive: cts.contains(text, caseInsensitive),
diacriticSensitive: cts.contains(text, diacriticSensitive),
diacriticInsensitive: cts.contains(text, diacriticInsensitive)
};
({
sample: 'cts/text-query-building-starter-pack/assets/cts-word-query-case-and-diacritic.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><caseInsensitiveMatches>true</caseInsensitiveMatches><caseSensitiveMatches>false</caseSensitiveMatches><diacriticInsensitiveMatches>true</diacriticInsensitiveMatches><diacriticSensitiveMatches>false</diacriticSensitiveMatches></result>
| Field | Value |
|---|---|
| XPath | /result |
| Payload | <result><caseInsensitiveMatches>true</caseInsensitiveMatches><caseSensitiveMatches>false</caseSensitiveMatches><diacriticInsensitiveMatches>true</diacriticInsensitiveMatches><diacriticSensitiveMatches>false</diacriticSen... |
What to notice: these options only make sense when you understand the word-search configuration behind the target database. See Word Search Configuration Settings That Affect These Samples.
cts:word-query wildcard behaviour
Wildcard queries are useful, but they are heavily dependent on database-level wildcard settings. This is exactly where short patterns (2 and 3 character prefixes) and trailing wildcards can behave differently between environments.
| Option / Argument | What it controls | Used here |
|---|---|---|
"wildcarded" | Treats * and ? as wildcard operators | Compared using ll* and lla* patterns |
"unwildcarded" | Treats * and ? as literal characters | Used as control comparison |
{
"envelope": {
"headers": { "type": "llamas" },
"instance": {
"llamas": {
"id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
"name": "Aaron",
"heightCm": 180,
"weightKg": 138,
"eyeColor": "Amber",
"hairColor": "Blonde",
"breed": "Huacaya",
"placeOfBirth": "Cusco, Peru",
"interests": ["playing chess", "birdwatching", "writing poetry"],
"medicalCondition": null,
"relatedTo": {
"id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
"relationship": "father"
},
"description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
"secretPower": {
"name": "Snake Charmer",
"id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
}
}
}
}
}
xquery version "1.0-ml";
let $twoCharPattern := "ll*"
let $threeCharPattern := "lla*"
let $twoCharWildcarded := count(
cts:uris(
(),
(),
cts:and-query((
cts:collection-query("wild-llamas"),
cts:word-query($twoCharPattern, ("wildcarded", "case-insensitive"))
))
)
)
let $twoCharLiteral := count(
cts:uris(
(),
(),
cts:and-query((
cts:collection-query("wild-llamas"),
cts:word-query($twoCharPattern, ("unwildcarded", "case-insensitive"))
))
)
)
let $threeCharWildcarded := count(
cts:uris(
(),
(),
cts:and-query((
cts:collection-query("wild-llamas"),
cts:word-query($threeCharPattern, ("wildcarded", "case-insensitive"))
))
)
)
return
<result>
<collection>wild-llamas</collection>
<twoCharPattern>{$twoCharPattern}</twoCharPattern>
<twoCharWildcardedCount>{$twoCharWildcarded}</twoCharWildcardedCount>
<twoCharLiteralCount>{$twoCharLiteral}</twoCharLiteralCount>
<threeCharPattern>{$threeCharPattern}</threeCharPattern>
<threeCharWildcardedCount>{$threeCharWildcarded}</threeCharWildcardedCount>
</result>
'use strict';
const query = cts.andQuery([
cts.collectionQuery('llamaverse'),
cts.wordQuery('llam*', ['wildcarded'])
]);
const results = cts.uris(null, ['limit=10'], query).toArray();
({
sample: 'cts/text-query-building-starter-pack/assets/cts-word-query-wildcarded-options.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><collection>wild-llamas</collection><twoCharPattern>ll*</twoCharPattern><twoCharWildcardedCount>4561</twoCharWildcardedCount><twoCharLiteralCount>0</twoCharLiteralCount><threeCharPattern>lla*</threeCharPattern><threeCharWildcardedCount>3001</threeCharWildcardedCount></result>
| Field | Value |
|---|---|
| XPath | /result |
| Payload | <result><collection>wild-llamas</collection><twoCharPattern>ll*</twoCharPattern><twoCharWildcardedCount>4561</twoCharWildcardedCount><twoCharLiteralCount>0</twoCharLiteralCount><threeCharPattern>lla*</threeCharPattern><t... |
What to notice: if trailing wildcard searches or short-character wildcard indexes are disabled, wildcarded counts can drop sharply or look inconsistent between databases. See Word Search Configuration Settings That Affect These Samples.
cts:word-query stemmed vs unstemmed
Stemming is the difference between asking for the exact surface form and asking MarkLogic to treat related word forms as the same search idea. That is useful when you want broad recall, but it is also the fastest way to make a search feel noisier than you intended.
This example searches for sing — a term with obvious morphological variants (sang, sung, singing). The contrast between stemmed and unstemmed counts is immediate.
| Option / Argument | What it controls | Used here |
|---|---|---|
"stemmed" | Broadens matches to morphological variants | Used in the broader variant query |
"unstemmed" | Restricts matches to exact word forms | Used in the strict variant query |
{
"envelope": {
"headers": { "type": "llamas" },
"instance": {
"llamas": {
"id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
"name": "Aaron",
"heightCm": 180,
"weightKg": 138,
"eyeColor": "Amber",
"hairColor": "Blonde",
"breed": "Huacaya",
"placeOfBirth": "Cusco, Peru",
"interests": ["playing chess", "birdwatching", "writing poetry"],
"medicalCondition": null,
"relatedTo": {
"id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
"relationship": "father"
},
"description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
"secretPower": {
"name": "Snake Charmer",
"id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
}
}
}
}
}
xquery version "1.0-ml";
let $stemmed-query := cts:and-query((
cts:collection-query("wild-llamas"),
cts:word-query("sing", ("stemmed"))
))
let $unstemmed-query := cts:and-query((
cts:collection-query("wild-llamas"),
cts:word-query("sing", ("unstemmed"))
))
return
<comparison>
<stemmed>
<count>{ cts:estimate($stemmed-query) }</count>
<samples>{
for $uri in cts:uris((), (), $stemmed-query)[1 to 3]
return <uri>{ $uri }</uri>
}</samples>
</stemmed>
<unstemmed>
<count>{ cts:estimate($unstemmed-query) }</count>
<samples>{
for $uri in cts:uris((), (), $unstemmed-query)[1 to 3]
return <uri>{ $uri }</uri>
}</samples>
</unstemmed>
</comparison>
'use strict';
const stemmed = cts.andQuery([
cts.collectionQuery('llamaverse'),
cts.wordQuery('sing', ['stemmed'])
]);
const unstemmed = cts.andQuery([
cts.collectionQuery('llamaverse'),
cts.wordQuery('sing', ['unstemmed'])
]);
const results = {
stemmedEstimate: cts.estimate(stemmed),
unstemmedEstimate: cts.estimate(unstemmed),
sampleUris: cts.uris(null, ['limit=10'], stemmed).toArray()
};
({
sample: 'cts/text-query-building-starter-pack/assets/cts-word-query-stemmed-vs-unstemmed.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
});
stemmed count: 992
unstemmed count: 0
stemmed sample URIs:
/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/0043e95e-3209-4b97-8871-f9b7073377f2.json
| # | URI |
|---|---|
| 1 | stemmed count: 992 |
| 2 | unstemmed count: 0 |
| 3 | stemmed sample URIs: |
| 4 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json |
| 5 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json |
| 6 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json |
What to notice: sing as an exact string appears nowhere in the llamaverse — all 992 matches are morphological variants (sang, sung, singing). Without stemming, the result count drops to zero. That is the difference between broad recall and exact-form precision.
cts:word-query synonym matches
Synonym matching is what you reach for when your search vocabulary leans on a controlled vocabulary or thesaurus. This sample compares a plain word query with the same query using the "synonym" option, so you can immediately see whether your current configuration expands the term set.
| Option / Argument | What it controls | Used here |
|---|---|---|
"synonym" | Treats equivalent terms as matching concepts | Enabled for synonym-aware match behaviour |
{
"envelope": {
"headers": { "type": "llamas" },
"instance": {
"llamas": {
"id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
"name": "Aaron",
"heightCm": 180,
"weightKg": 138,
"eyeColor": "Amber",
"hairColor": "Blonde",
"breed": "Huacaya",
"placeOfBirth": "Cusco, Peru",
"interests": ["playing chess", "birdwatching", "writing poetry"],
"medicalCondition": null,
"relatedTo": {
"id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
"relationship": "father"
},
"description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
"secretPower": {
"name": "Snake Charmer",
"id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
}
}
}
}
}
xquery version "1.0-ml";
let $plain-query := cts:and-query((
cts:collection-query("wild-llamas"),
cts:word-query("hiking")
))
let $synonym-query := cts:and-query((
cts:collection-query("wild-llamas"),
cts:word-query("hiking", "synonym")
))
return
<result>
<plain-count>{ cts:estimate($plain-query) }</plain-count>
<synonym-count>{ cts:estimate($synonym-query) }</synonym-count>
<samples>{
for $uri in cts:uris((), (), $synonym-query)[1 to 10]
return <uri>{ $uri }</uri>
}</samples>
</result>
'use strict';
const plainQuery = cts.andQuery([
cts.collectionQuery('llamaverse'),
cts.wordQuery('hiking')
]);
const synonymQuery = cts.andQuery([
cts.collectionQuery('llamaverse'),
cts.wordQuery('hiking', ['synonym'])
]);
const results = {
plainEstimate: cts.estimate(plainQuery),
synonymEstimate: cts.estimate(synonymQuery),
uris: cts.uris(null, ['limit=10'], synonymQuery).toArray()
};
({
sample: 'cts/text-query-building-starter-pack/assets/cts-word-query-synonym.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><plain-count>1002</plain-count><synonym-count>1002</synonym-count><samples><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48148c.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00863414-306e-49cb-acd0-6579bea5421e.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00a0fe3a-da61-493c-990e-af7719db0d18.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00cf82a3-4937-40c6-80fe-6ae0f1100364.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00d80680-7f0c-4613-a58d-db38fcdbc22c.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/014566d5-d9c8-4caa-a5f8-07a929e004f0.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/019653d9-e191-4e09-a03d-d9f6d395ab3e.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/01a9a1bc-e506-495c-9f1d-6984b1b5f68d.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/01be86ce-b01a-42f3-9795-b182a85aa925.json</uri></samples></result>
| Field | Value |
|---|---|
| XPath | /result |
| Payload | <result><plain-count>1002</plain-count><synonym-count>1002</synonym-count><samples><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json</uri><uri>/cleverllamas/llamaverse/raw/wil... |
What to notice: in this environment, plain-count and synonym-count are the same, which tells you no extra synonym expansion is currently being applied for this term. With a configured thesaurus, synonym-count is where you should expect the wider match set. See Word Search Configuration Settings That Affect These Samples for the key environment assumptions.
cts:or-query
When business logic says "either works" and your query finally agrees.
Source docs: https://docs.marklogic.com/11.0/cts:or-query
| Option / Argument | What it controls | Used here |
|---|---|---|
$queries | List of alternative sub-queries; any match satisfies the clause | Multiple term alternatives in one composed query |
{
"envelope": {
"headers": { "type": "llamas" },
"instance": {
"llamas": {
"id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
"name": "Aaron",
"heightCm": 180,
"weightKg": 138,
"eyeColor": "Amber",
"hairColor": "Blonde",
"breed": "Huacaya",
"placeOfBirth": "Cusco, Peru",
"interests": ["playing chess", "birdwatching", "writing poetry"],
"medicalCondition": null,
"relatedTo": {
"id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
"relationship": "father"
},
"description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
"secretPower": {
"name": "Snake Charmer",
"id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
}
}
}
}
}
xquery version "1.0-ml";
let $query := cts:and-query((
cts:collection-query("wild-llamas"),
cts:or-query((
cts:word-query("llama"),
cts:word-query("suri")
))
))
return cts:uris((), (), $query)[1 to 20]
'use strict';
const query = cts.andQuery([
cts.collectionQuery('llamaverse'),
cts.orQuery([
cts.wordQuery('llama'),
cts.wordQuery('alpaca')
])
]);
const results = cts.uris(null, ['limit=10'], query).toArray();
({
sample: 'cts/text-query-building-starter-pack/assets/cts-or-query.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
});
/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
/cleverllamas/llamaverse/raw/wild-llamas/llamas/002c7133-e985-4bd6-9ea8-f057fa0c6ee7.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48148c.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/007229e2-ee25-47c1-b18a-2e0c0ec0dbcd.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00863414-306e-49cb-acd0-6579bea5421e.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/0087ad24-a720-4d82-b513-79ae5feebcc6.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/009cf2b9-d4db-4491-9cc2-34e2f43a5ee1.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00a0fe3a-da61-493c-990e-af7719db0d18.json
/cleverllamas/llamaverse/raw/wild-llamas/llamas/00cf82a3-4937-40c6-80fe-6ae0f1100364.json
... (8 more URIs)
| # | URI |
|---|---|
| 1 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/00048384-cb13-4557-805e-a6b4e57f7eab.json |
| 2 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/0005e261-4302-4ae8-9574-732a54040423.json |
| 3 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/000eca10-d166-469f-b17a-3c3b35ee0883.json |
| 4 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/002c7133-e985-4bd6-9ea8-f057fa0c6ee7.json |
| 5 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json |
| 6 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48148c.json |
| 7 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/007229e2-ee25-47c1-b18a-2e0c0ec0dbcd.json |
| 8 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/00863414-306e-49cb-acd0-6579bea5421e.json |
| 9 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/0087ad24-a720-4d82-b513-79ae5feebcc6.json |
| 10 | /cleverllamas/llamaverse/raw/wild-llamas/llamas/009cf2b9-d4db-4491-9cc2-34e2f43a5ee1.json |
What to notice: cts:or-query is ideal for alternate term paths without duplicating surrounding query constraints.
cts:not-query
Sometimes the fastest way to find the right llamas is to exclude the wrong ones first.
Source docs: https://docs.marklogic.com/11.0/cts:not-query
| Option / Argument | What it controls | Used here |
|---|---|---|
$query | Sub-query whose matches should be excluded | Negative clause used to remove unwanted matches |
{
"envelope": {
"headers": { "type": "llamas" },
"instance": {
"llamas": {
"id": "0c8bdb0d-ac62-49b7-ac74-94dbba46efa5",
"name": "Aaron",
"heightCm": 180,
"weightKg": 138,
"eyeColor": "Amber",
"hairColor": "Blonde",
"breed": "Huacaya",
"placeOfBirth": "Cusco, Peru",
"interests": ["playing chess", "birdwatching", "writing poetry"],
"medicalCondition": null,
"relatedTo": {
"id": "a6c69bb3-fa75-4327-bed6-1f0623ae2c6c",
"relationship": "father"
},
"description": "Aaron is an amber-eyed llama with blonde hair, standing 180 cm tall. Originally from Cusco, Peru, Aaron enjoys playing chess, birdwatching, and writing poetry.",
"secretPower": {
"name": "Snake Charmer",
"id": "f6236908-9c0e-416e-a591-a1bd3986dc05"
}
}
}
}
}
xquery version "1.0-ml";
let $query := cts:and-query((
cts:collection-query("wild-llamas"),
cts:word-query("hiking"),
cts:not-query(cts:word-query("dancing"))
))
let $count := cts:estimate($query)
let $samples := cts:uris((), (), $query)[1 to 10]
return
<result>
<count>{ $count }</count>
<samples>{
for $uri in $samples
return <uri>{ $uri }</uri>
}</samples>
</result>
'use strict';
const query = cts.andQuery([
cts.collectionQuery('llamaverse'),
cts.wordQuery('hiking'),
cts.notQuery(cts.wordQuery('dancing'))
]);
const results = {
estimate: cts.estimate(query),
uris: cts.uris(null, ['limit=10'], query).toArray()
};
({
sample: 'cts/text-query-building-starter-pack/assets/cts-not-query.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><count>760</count><samples><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48148c.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00a0fe3a-da61-493c-990e-af7719db0d18.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/00cf82a3-4937-40c6-80fe-6ae0f1100364.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/014566d5-d9c8-4caa-a5f8-07a929e004f0.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/019653d9-e191-4e09-a03d-d9f6d395ab3e.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/01a9a1bc-e506-495c-9f1d-6984b1b5f68d.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/01be86ce-b01a-42f3-9795-b182a85aa925.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/02917e85-3ab6-4330-8faa-e4f3f58e4ff1.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/02eb04d8-70b3-4acc-b66e-a78af69cfb85.json</uri></samples></result>
| Field | Value |
|---|---|
| XPath | /result |
| Payload | <result><count>760</count><samples><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/0043e95e-3209-4b97-8871-f9b7073377f2.json</uri><uri>/cleverllamas/llamaverse/raw/wild-llamas/llamas/006c5ffa-6c5e-4169-8a08-eec49f48... |
What to notice: this exclusion is selective. You are not removing an unrelated edge term; you are intentionally narrowing from "hiking" to "hiking but not dancing", which makes the negative clause meaningful.
For this particular query, a plain URI list is not as helpful as a summary because the result set is large enough that the interesting question is "how many?" first, and "which ones?" second. That is why the sample output above shows a count plus a few representative URIs.
Word Search Configuration Settings That Affect These Samples
These examples are intentionally explicit about query options, but the final behaviour still depends on database configuration. If your results differ from this starter pack, check these settings first.
| Setting area | Why it matters | What to verify |
|---|---|---|
| Default collation and language | Affects term comparison and linguistic behaviour | Ensure the content database collation/language are the expected values for your text corpus |
| Stemming settings | Controls broadening from root forms to variants | Confirm stemming behaviour matches your expectation for the selected language |
| Trailing wildcard searches | Controls whether patterns like llam* are expanded as wildcards | Verify trailing wildcard searches are enabled when using wildcard-heavy patterns |
| 2-character and 3-character wildcard support | Controls whether very short wildcard prefixes are indexed efficiently | Check short wildcard settings for 2/3 character terms if you use ll* or similar prefixes |
| Word positions / tokenisation | Affects phrase, punctuation, and whitespace-sensitive matching | Confirm tokenisation and word position behaviour in your environment |
| Thesaurus/synonym data | Controls synonym expansion quality | Ensure synonym data exists and is appropriate before relying on "synonym" |
Operational guidance for this and related packs:
- Validate wildcard and stemming settings in the target content database before tuning query options.
- Re-run a small control query after any collation/language change.
- Treat environment-level search configuration as part of the code sample contract, not as a background detail.
Think This is Powerful? Keep Going.
Decision rule: lock your text option defaults (stemming, wildcard, sensitivity) before scaling query logic across endpoints.
These text-building skills unlock even more sophisticated patterns:
- Search Composition Starter Pack — Combine the queries you've learned here into larger, production-ready compositions.
- Relevance and Scoring Starter Pack — See how your query choices influence the ranking order of results.
- URI and Collection Auditing Starter Pack — Combine text queries with collection and URI filtering for surgical result targeting.
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!
- API Context for This Pack
- Functions in This Pack
- cts:word-query
- cts:word-query documented option interactions
- cts:word-query case and diacritic sensitivity
- cts:word-query wildcard behaviour
- cts:word-query stemmed vs unstemmed
- cts:word-query synonym matches
- cts:or-query
- cts:not-query
- Word Search Configuration Settings That Affect These Samples
- Think This is Powerful? Keep Going.