Query Parameters
Parameters are the only safe way to mix host-side values into a query. Every in-process binding accepts them; the HTTP transport does not yet (see caveat).
MATCH (u:User) WHERE u.id = $id RETURN u
$id is a placeholder. The host supplies its value at call time.
Why parameters
- Safety. Parameters cannot rewrite the query shape — no injection. Inlining untrusted values is unsafe.
- Cacheability. The parser and analyzer can reuse the same plan
across calls with different
$idvalues. - Type fidelity. Typed values (dates, points, bigints) survive the trip — no string round-trip through the query text.
Binding parameters by host
Rust
use std::collections::BTreeMap;
use lora_database::{Database, LoraValue};
let db = Database::in_memory();
let mut params: BTreeMap<String, LoraValue> = BTreeMap::new();
params.insert("name".into(), LoraValue::String("Ada".into()));
params.insert("min".into(), LoraValue::Int(1800));
db.execute_with_params(
"MATCH (p:Person)
WHERE p.name = $name AND p.born >= $min
RETURN p.name AS name",
None,
params,
)?;
More detail: Rust → Parameterised query.
Node / TypeScript
await db.execute(
"MATCH (p:Person) WHERE p.name = $name RETURN p",
{ name: 'Ada' }
);
More detail: Node → Parameterised query.
Python
db.execute(
"MATCH (p:Person) WHERE p.name = $name RETURN p",
{"name": "Ada"},
)
More detail: Python → Parameterised query.
WASM
await db.execute(
"MATCH (u:User) WHERE u.handle = $handle RETURN u",
{ handle: 'alice' }
);
More detail: WASM → Parameterised query.
Go
db.Execute(
"MATCH (u:User) WHERE u.handle = $handle RETURN u",
lora.Params{"handle": "alice"},
)
More detail: Go → Parameterised query.
Ruby
db.execute(
"MATCH (u:User) WHERE u.handle = $handle RETURN u",
{ handle: "alice" },
)
More detail: Ruby → Parameterised query.
Host → LoraDB type mapping
| Host value | LoraDB type |
|---|---|
null / None / undefined | Null |
bool / boolean | Boolean |
int / integer number / i64 | Integer |
float / non-integer number / f64 | Float |
str / String | String |
list / array / Vec | List |
dict / object / BTreeMap | Map |
date() helper | Date |
datetime() / localdatetime() helper | DateTime / LocalDateTime |
duration() helper | Duration |
wgs84() / cartesian() helper | Point |
vector() helper / tagged object | Vector |
Missing entries resolve to null. The engine doesn't raise on an
unbound parameter — it silently filters everything out. Audit bindings
when a query returns no rows. See
Troubleshooting → Silent filter from an unbound parameter.
Where parameters can appear
| Position | Supported |
|---|---|
Expression / literal (p.age = $age) | ✓ |
Inline map property ({id: $id}) | ✓ |
List expression ($ids) | ✓ |
UNWIND $rows AS row | ✓ |
| Pattern label / relationship type | ✗ — see Limitations |
| Property key name | ✗ |
The unsupported positions would let a parameter rewrite the query shape. If you genuinely need a dynamic label, compose the query string host-side from a trusted allow-list — never from raw input. See Limitations → Parameters.
Common patterns
Bulk load from a list
UNWIND $rows AS row
CREATE (:User {id: row.id, name: row.name})
await db.execute(
"UNWIND $rows AS row CREATE (:User {id: row.id, name: row.name})",
{ rows: [{ id: 1, name: 'Ada' }, { id: 2, name: 'Grace' }] }
);
See UNWIND → bulk load.
Dynamic IN-style filter
MATCH (u:User) WHERE u.id IN $ids RETURN u
db.execute(
"MATCH (u:User) WHERE u.id IN $ids RETURN u",
{"ids": [1, 2, 3, 4]},
)
Pass-through typed values
import { wgs84, duration } from '@loradb/lora-node';
await db.execute(
"CREATE (:Trip {origin: $here, span: $span})",
{ here: wgs84(4.89, 52.37), span: duration('PT90M') }
);
Semantic retrieval with a vector parameter
Build a tagged VECTOR with the helper for your language, pass it as
an ordinary parameter, and score it against stored embeddings:
import { vector } from '@loradb/lora-node';
const q = vector(embedding, 384, 'FLOAT32');
await db.execute(
`MATCH (d:Doc)
RETURN d.id AS id
ORDER BY vector.similarity.cosine(d.embedding, $q) DESC
LIMIT 10`,
{ q },
);
The same helper exists in every in-process binding — see the Vectors → Passing vectors as parameters table for Python, Go, Ruby, and Rust shapes.
vector.similarity.cosine and vector.similarity.euclidean also
accept a plain LIST<NUMBER> on either side, so for a one-off query
you can skip the helper and pass { q: [0.1, 0.2, 0.3] } — the list
is coerced to a FLOAT32 vector whose dimension equals its length.
The full tagged helper is required only when the vector will be
stored as a property, because property storage needs the complete
{kind, dimension, coordinateType, values} shape.
Vector indexes are not implemented yet, so the query above is a linear
scan over every matched Doc — fine for small datasets, not for
production-scale retrieval until index support ships.
Default a missing value host-side
LoraDB doesn't have "default parameter values" — bind null
explicitly (or use coalesce
in the query) when the caller omits a field:
await db.execute(
"MATCH (u:User) WHERE u.tier = coalesce($tier, u.tier) RETURN u",
{ tier: opts?.tier ?? null }
);
HTTP API doesn't forward params
POST /query currently ignores any params body field. Bind via one
of the in-process bindings (Rust, Node, Python, WASM, Go, or Ruby), or
build the literal into the query string when values are trusted and
encoded. Parameters over HTTP are on the roadmap — see
Limitations → Parameters.
If you must use HTTP today with dynamic values, serialise the value into the query yourself via a trusted encoder:
NAME='Ada'
curl -s http://127.0.0.1:4747/query \
-H 'content-type: application/json' \
--data-binary "$(jq -n --arg q "MATCH (p:Person {name: '$NAME'}) RETURN p" \
'{query: $q}')"
For anything user-supplied, run against a local binding with real parameters and expose a narrower API on top.
Common mistakes
Unbound parameter
The query parses, runs, returns zero rows. Cause: the host didn't
bind $id at all. Fix: audit the params map, or validate inputs
before executing.
Wrong type
{id: $id} with host value "1" (a string) won't match an integer
property. Use the right host type, or coerce inside the query:
MATCH (n:User) WHERE toString(n.id) = $id RETURN n
Inlining untrusted input
// Don't do this
await db.execute(`MATCH (u:User {name: '${req.query.name}'}) RETURN u`);
Use $name and pass { name: req.query.name } instead. Parameters
are the only supported safe mixing mechanism.
See also
- Queries → Overview — pipeline and clauses.
- Data Types → Overview — every value the engine understands.
- UNWIND + MERGE — bulk load and upserts with parameters.
- Troubleshooting → Parameters — silent filtering, HTTP ignore, integer precision.
- Limitations → Parameters — HTTP and identifier positions.