String Functions
Every function on this page returns null when any argument is
null. Case operations are ASCII-only; normalize is a no-op
placeholder.
Overview
| Goal | Function |
|---|---|
| Case conversion | toLower, toUpper |
| Trim whitespace | trim, lTrim, rTrim |
| Substring replace | replace |
| Substring slice | substring |
| Left / right slice | left, right |
| Split by delimiter | split |
| Reverse | reverse |
| Length | size, length, charLength |
| Pad | lpad, rpad |
| Normalise (NFC) | normalize (placeholder) |
| Convert type | toString, toInteger, toFloat, toBoolean |
Predicate in WHERE | STARTS WITH, ENDS WITH, CONTAINS, =~ |
toLower / toUpper
ASCII case conversion.
RETURN toLower('Ada LoveLace') -- 'ada lovelace'
RETURN toUpper('ada') -- 'ADA'
Non-ASCII letters pass through unchanged:
RETURN toLower('Ångström') -- 'Ångström' (non-ASCII untouched)
Case-insensitive matching
MATCH (u:User)
WHERE toLower(u.email) = toLower($search)
RETURN u
MATCH (u:User)
WHERE toLower(u.name) STARTS WITH toLower($prefix)
RETURN u
trim / lTrim / rTrim
Strip whitespace from both ends / left / right.
RETURN trim(' hi ') -- 'hi'
RETURN lTrim(' hi ') -- 'hi '
RETURN rTrim(' hi ') -- ' hi'
Common for cleaning up user input before storage:
UNWIND $rows AS row
CREATE (:Contact {email: toLower(trim(row.email))})
replace
replace(str, find, replacement) — replaces every occurrence.
RETURN replace('banana', 'a', 'o') -- 'bonono'
RETURN replace('hello', 'x', 'y') -- 'hello'
RETURN replace('abc def', ' ', '_') -- 'abc_def'
Multi-step replace
WITH 'Joe O\'Brien' AS raw
RETURN replace(replace(raw, ' ', '_'), '\'', '') AS slug
-- 'Joe_OBrien'
substring
substring(str, start[, length]) — 0-based indices.
RETURN substring('loradb', 0, 4) -- 'lora'
RETURN substring('loradb', 4) -- 'db'
RETURN substring('hello', 1, 3) -- 'ell'
Out-of-range indices return an empty string rather than an error.
RETURN substring('hi', 99) -- ''
RETURN substring('hi', 0, 99) -- 'hi'
left / right
RETURN left('graphdb', 5) -- 'graph'
RETURN right('graphdb', 2) -- 'db'
Length exceeding the input returns the whole string:
RETURN left('ab', 99) -- 'ab'
split
RETURN split('a,b,c,d', ',') -- ['a', 'b', 'c', 'd']
RETURN split('one two three', ' ') -- ['one', 'two', 'three']
RETURN split('x', ',') -- ['x']
Empty input returns [''].
Split + UNWIND
Turn comma-separated values into rows:
UNWIND split('red,green,blue', ',') AS color
CREATE (:Swatch {color: color})
reverse
Works on both strings and lists.
RETURN reverse('hello') -- 'olleh'
RETURN reverse([1, 2, 3]) -- [3, 2, 1]
size / length / charLength
| Function | Measures |
|---|---|
size(s) | Length of the string (bytes for ASCII-only; code units otherwise) |
length(s) | Alias for size on strings; also accepts paths |
charLength(s) | Unicode code-point count |
RETURN size('abc') -- 3
RETURN length('abc') -- 3
RETURN charLength('café') -- 4
length also accepts paths — see
Paths → path functions.
lpad / rpad
lpad(str, length, padding) / rpad(str, length, padding) — pads to
the target length using the padding character repeated.
RETURN lpad('7', 3, '0') -- '007'
RETURN rpad('7', 3, '0') -- '700'
RETURN lpad('abc', 5, '.') -- '..abc'
RETURN rpad('abc', 5, '.') -- 'abc..'
If the input is already longer than length, it's returned unchanged.
Fixed-width formatting
MATCH (r:Record)
RETURN lpad(toString(r.id), 6, '0') AS padded_id
normalize
Placeholder for Unicode NFC normalisation. Today it returns the input unchanged.
RETURN normalize('café') -- 'café' (no NFC applied)
If you need real NFC normalisation, apply it host-side before writing.
Type conversion
| Function | Accepts | Returns |
|---|---|---|
toString(x) | any | String; null → null |
toInteger(x) / toInt(x) | Int, Float (truncates), String, Bool | Int or null on parse failure |
toFloat(x) | Int, Float, String | Float or null on parse failure |
toBoolean(x) / toBooleanOrNull(x) | Bool, String ("true"/"false"), Int (0 / non-0) | Bool or null on parse failure |
RETURN toString(42) -- '42'
RETURN toString(true) -- 'true'
RETURN toString(date('2024-01-15')) -- '2024-01-15'
RETURN toInteger('007') -- 7
RETURN toInteger(3.9) -- 3 (truncates)
RETURN toInteger(true) -- 1
RETURN toInteger('not a number') -- null (parse fails)
RETURN toFloat('3.14') -- 3.14
RETURN toFloat(42) -- 42.0
RETURN toBoolean('TRUE') -- true
RETURN toBoolean(0) -- false
RETURN toBoolean('maybe') -- null
Safe conversion pattern
Combine with coalesce for a
default on parse failure:
MATCH (p:Product) RETURN coalesce(toInteger(p.stock), 0) AS stock
String operators (in WHERE)
Covered in the WHERE page —
included here for completeness:
| Operator | Case-sensitive | Description |
|---|---|---|
STARTS WITH | yes | Prefix match |
ENDS WITH | yes | Suffix match |
CONTAINS | yes | Substring match |
=~ | yes | Regex match (Rust regex, RE2-style — no backreferences) |
MATCH (u:User) WHERE u.email ENDS WITH '@loradb.com' RETURN u
MATCH (u:User) WHERE toLower(u.email) =~ '.*@loradb\\.com$' RETURN u
MATCH (u:User) WHERE u.name CONTAINS 'Admin' RETURN u
Regex vs CONTAINS
Regex is more expressive but slower and strict-anchored (=~ 'foo'
matches only the full string foo). Prefer CONTAINS for simple
substring matches.
Common patterns
Slugify
WITH 'Hello, World! 2024' AS raw
RETURN toLower(replace(replace(raw, ',', ''), ' ', '-')) AS slug
-- 'hello--world!-2024'
Not a full slugifier — punctuation survives. For real slugs, normalise host-side.
Initials
MATCH (p:Person) WHERE p.name IS NOT NULL
RETURN p.name,
reduce(acc = '', part IN split(p.name, ' ') |
acc + left(part, 1)) AS initials
Domain from email
MATCH (u:User) WHERE u.email CONTAINS '@'
RETURN u.email,
substring(u.email, size(split(u.email, '@')[0]) + 1) AS domain
Normalise for comparison
MATCH (u:User)
WHERE toLower(trim(u.email)) = toLower(trim($candidate))
RETURN u
Join a list into a string
There's no join function. Use
reduce:
MATCH (u:User)
RETURN u.name,
reduce(out = '', t IN u.tags |
CASE WHEN out = '' THEN t ELSE out + ', ' + t END
) AS tags_csv
Parse key=value pairs
WITH 'a=1;b=2;c=3' AS s
RETURN reduce(
m = {},
pair IN split(s, ';') |
m + {[split(pair, '=')[0]]: split(pair, '=')[1]}
) AS parsed
-- {a: '1', b: '2', c: '3'}
Values are strings — wrap each with
toInteger if you need numeric
types.
Truncate for preview
MATCH (p:Post)
RETURN p.id,
CASE WHEN size(p.body) > 100
THEN left(p.body, 97) + '...'
ELSE p.body
END AS preview
The conditional here is a CASE
expression — LoraDB's ternary. See that page for the full reference.
Limitations
toLower/toUpperare ASCII-only. For Unicode case folding, normalise host-side before passing the string to LoraDB.normalizeis not yet implemented — it's a placeholder that returns its input unchanged.- String indexing is byte-based inside
sizeandlength. UsecharLengthwhen you need Unicode code-point counts.
See also
- Scalars → String — literal syntax and comparison.
- WHERE → String matching.
- Lists —
splitreturns a list. - Functions → Overview —
toString,toInteger, etc.