Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Procedures (CALL)

patinaDB has an extensible procedure framework. Procedures are invoked with CALL, declare a fixed set of named yield columns, and stream rows back into the query like any other operator. A trailing CALL with no RETURN implicitly returns all yielded columns.

CALL patinadb.engrams() YIELD id, message, timestamp
RETURN id, message ORDER BY timestamp DESC

You can register your own read-only WASM procedures — see WASM Procedures below. Everything under Built-in procedures is a built-in, always-available procedure; user-defined ones are called as CALL wasm.<name>(...) after a CREATE FUNCTION … LANGUAGE wasm DDL statement.

Bulk write clause: CALL { … } IN TRANSACTIONS

A sibling of CALL worth knowing alongside the procedures below: wrapping a mutating subquery in CALL { … } IN TRANSACTIONS [OF n ROW[S]] [ON ERROR {CONTINUE|BREAK|FAIL}] chunks its writes into many small, independently committed transactions instead of one giant one — the batched-ingest answer to a LOAD CSV … CREATE memory cliff.

LOAD CSV WITH HEADERS FROM 'file:///data/accounts.csv' AS row
CALL {
  WITH row
  CREATE (:Account {id: toInteger(row.id), name: row.name})
} IN TRANSACTIONS OF 1000 ROWS

Each chunk is its own engram and, on a cluster, its own Raft entry (⌈rows/n⌉ bounded entries, not one unbounded one); a later chunk sees the writes of earlier ones (read-your-writes). ON ERROR FAIL (default) aborts the whole statement on a failing chunk; CONTINUE skips it and keeps going; BREAK stops after it, keeping earlier chunks. It must be the query’s final clause, and cannot run inside an explicit Bolt BEGIN … COMMIT. See Bulk Loading & Import.

User-defined procedures: WASM functions

Beyond the built-ins below, you can register your own read-only procedures compiled to WebAssembly:

CREATE FUNCTION fib (1) LANGUAGE wasm FROM 'file:///opt/patinadb/fib.wasm' EXPORT 'fib';
CALL wasm.fib(10) YIELD result RETURN result;  -- 55

A WASM function runs deterministically and sandboxed (no filesystem, network, clock, or randomness), which is what makes it safe to replicate across a cluster — CREATE/DROP FUNCTION are DDL that replicate by value (compiled module bytes, not the source path) and ride Raft snapshots. See WASM Procedures for the full registration grammar, the ABI, and the replication/RBAC model.

Built-in procedures

History & diff

ProcedureYieldsPurpose
patinadb.engrams()id, message, timestamp, …List committed engrams (newest first).
patinadb.diff(id)per-operation rowsGit-style view of a single engram.
patinadb.diffRange(from, to)structural change rowsMove-aware structural diff between two points.

See Engrams and Diffs.

Schema introspection

ProcedureYieldsDescription
db.labels()labelEvery distinct node label in the graph (primary and secondary).
db.relationshipTypes()relationshipTypeEvery distinct relationship type.
db.propertyKeys()propertyKeyEvery distinct property key across nodes and relationships.

These are read-only and run over every surface (REST, Bolt) via the same CALL path — not a driver/browser-only shim.

Registered under two namespaces, like full-text search:

ProcedureAliasYieldsDescription
db.index.vector.queryNodes(name, k, queryVector)patinadb.vector.queryNodesnode, scorek-nearest-neighbour search over a vector index (IVF-Flat ANN, cosine or Euclidean similarity). Yields real nodes.

See Vector Search for index creation, the similarity functions, and the ANN accuracy trade-off.

Registered under two namespaces — the Neo4j-compatible name and a patinaDB alias — so existing Neo4j tooling works unchanged:

ProcedureAliasYields
db.index.fulltext.queryNodes(name, query)patinadb.fulltext.queryNodesnode, score
db.index.fulltext.queryRelationships(name, query)patinadb.fulltext.queryRelationshipsrelationship, score

queryNodes yields real nodes (not just property maps), so they render as graph nodes in the Neo4j Browser and hydrate correctly through Bolt drivers:

CALL db.index.fulltext.queryNodes('docs', 'graph AND database~')
YIELD node, score
RETURN node.title, score ORDER BY score DESC LIMIT 10

See Full-Text Search for index creation and the query syntax.

Graph algorithms

A small set of read-only graph algorithms run over an in-memory snapshot of the current graph. Each takes an optional node-label and relationship-type projection (pass null to include everything) and yields real nodes plus a per-node result. gds.*.stream aliases are provided for tooling discoverability — note they use patinaDB’s simplified (label?, relType?, config?) signature, not Neo4j GDS’s named-graph-projection API.

ProcedureAliasYieldsPurpose
patinadb.algo.pageRank(label?, relType?, config?)gds.pageRank.streamnode, scoreIterative PageRank. config = {iterations: 20, dampingFactor: 0.85}. Scores sum to ≈ 1.0.
patinadb.algo.wcc(label?, relType?)gds.wcc.streamnode, componentIdWeakly-connected components. componentId is the smallest node UUID in the component (stable across runs).
patinadb.algo.degree(label?, relType?, config?)gds.degree.streamnode, scoreDegree centrality. config = {direction: 'both'} (in/out/both); a self-loop counts twice for both.
patinadb.algo.betweenness(label?, relType?)gds.betweenness.streamnode, scoreExact betweenness centrality (Brandes’ algorithm) over directed shortest paths. A path’s interior nodes score highest; O(V·E).
patinadb.algo.closeness(label?, relType?)gds.closeness.streamnode, scoreCloseness centrality over directed shortest paths, Wasserman–Faust normalized ((k/(N-1))·(k/Σd)) so disconnected graphs are well-defined; unreachable-only nodes score 0. O(V·E).
patinadb.algo.triangleCount(label?, relType?)gds.triangleCount.streamnode, trianglesPer-node triangle count over the undirected simple graph (self-loops/parallel edges collapsed). Global count = Σ triangles / 3.
patinadb.algo.labelPropagation(label?, relType?, config?)gds.labelPropagation.streamnode, communityIdDeterministic synchronous label-propagation community detection. config = {maxIterations: 10}. communityId = the smallest node UUID in the community (stable across runs, like WCC).
CALL patinadb.algo.pageRank('Page', 'LINKS', {iterations: 30})
YIELD node, score
RETURN node.title, score ORDER BY score DESC LIMIT 10

The algorithms materialize the projected graph in RAM (fine up to ~10M edges on one box), run in memory, and yield without mutating anything — so they need no replication and can run on any cluster node. Because PageRank is normalized to sum to 1, its ranking matches Neo4j but its magnitudes differ.

betweenness and closeness follow out-edges (directed shortest paths), matching PageRank/degree; triangleCount and labelPropagation treat edges as undirected. Every algorithm is deterministic — the crux for labelPropagation, which is normally randomized: patinaDB uses a fixed synchronous update with a lowest-label tie-break and a hard iteration cap (iterations/maxIterations are bounded at 1000 so a read call cannot become a compute DoS), so every cluster node computes the same communities. Because betweenness/closeness are O(V·E), they suit small-to-medium projections; the in-memory-only scale ceiling applies. APOC utility procedures and weighted/personalized PageRank variants are planned follow-ons.

Statistics catalog

ProcedureYieldsDescription
patinadb.stats(label?)label, property, count, ndv, min, maxThe query planner’s statistics catalog for a label (or all labels). One summary row per label (property = null, count = vertex count) plus one row per property with its present count, distinct-value count (NDV), and min/max.
CALL patinadb.stats('Person')
YIELD label, property, count, ndv, min, max
RETURN property, count, ndv, min, max ORDER BY ndv DESC

The catalog feeds cost-based entry-point selection: it lets the planner estimate how selective a WHERE/pattern filter is (e.g. a rare property value vs a common one) and drive a query from the cheapest starting point. It also feeds cost-based join ordering: a multi-pattern MATCH (a)…, (b)…, (c)… is reordered so the most selective pattern drives the join and intermediate results stay small, instead of joining patterns in written order. Statistics are local performance hints — they change only which plan runs, never the result (a different join order returns the same rows) — so they are computed lazily per node, cached, and never replicated.

Index advisor

ProcedureYieldsDescription
patinadb.advisor(query)suggestion, reason, current_planAnalyze a query string (parsed + planned exactly as EXPLAIN) and suggest an index that would flip it to a faster physical plan. Today it surfaces missing edge-sorted indexes for a traversal + ORDER BY target.prop LIMIT k shape; empty result means nothing to advise.
CALL patinadb.advisor(
  'MATCH (c:Company {name:"Acme"})<-[:WORKS_AT]-(p:Person) RETURN p ORDER BY p.age DESC LIMIT 3')
YIELD suggestion, reason, current_plan RETURN suggestion
-- suggestion: 'CREATE EDGE SORTED INDEX FOR (m:Person)<-[:WORKS_AT]-() ON m.age'

EXPLAIN prints the same advice as an Advice: footer. See Query Planning & Performance and Edge-Sorted Indexes.

Cache observability

ProcedureYieldsDescription
patinadb.cache.stats()scope, kind, bytes, entries, hit_rate, generationThe multi-level, RAM-budget-governed cache. One row per (level, scope): scope is the hot database / collection (label) / query shape (db:1/label:Ticket), kind is the cache level, and hit_rate is hits / (hits + misses). generation is reserved (yielded as null today).
CALL patinadb.cache.stats()
YIELD scope, kind, bytes, entries, hit_rate
RETURN scope, kind, bytes, hit_rate ORDER BY bytes DESC

Read-only and node-local. When caching is disabled (the default) it yields zero rows (no error). The same view is available over HTTP as GET /mgmt/cache and as Prometheus gauges on /metrics. For every metric and a tuning playbook see Cache Observability & Tuning.

CSV export

Write graph data out as neo4j-admin-style CSV — the same format GET /mgmt/export uses, so an exported file loads straight back with LOAD CSV/bulk upsert (:ID(uuid) reproduces vertex UUIDs, byte-identical).

ProcedureAliasYieldsDescription
patinadb.export.csv(label, path [, config])file, kind, rowsExport one label’s vertices to CSV. path ending .csv writes a single node file; otherwise path is a directory and gets nodes_<Label>.csv plus that label’s outgoing relationships as rels_<TYPE>.csv (one per edge type). config = {rels: false} skips relationships.
patinadb.export.query(query, file)apoc.export.csv.queryfile, kind, rowsExport an arbitrary query result (selective export). CSV columns are the RETURN names.
-- Whole label + its relationships into a directory:
CALL patinadb.export.csv('Person', '/data/exports/people')
YIELD file, kind, rows RETURN file, kind, rows

-- A selective slice via a query:
CALL patinadb.export.query(
  'MATCH (p:Person) WHERE p.age > 30 RETURN p.name AS name, p.age AS age',
  '/data/exports/over30.csv')

Node files carry an :ID(uuid) column, one typed column per property (name:int / :float / :boolean / :date / :localdatetime, or a bare string column) and a :LABEL column; an absent property is a blank cell. Relationship files carry :START_ID(uuid) / :END_ID(uuid) / :TYPE + typed property columns. Query-export entity cells (nodes/relationships) are rendered as their UUID / type — use patinadb.export.csv for a structural round-trip. Export streams row-by-row (bounded memory) over the current committed graph.

Security note. These procedures write a file on the machine running the query — the same file-write consideration as LOAD CSV reading a file:// URL. On the server both are deny-by-default: an export writes only to directories whitelisted with --allow-export-dir, and any file-I/O query is raised to require the global Admin role (details). The procedures are CSV-only; Parquet/Arrow export goes through GET /mgmt/export?format=parquet|arrow (see REST API).

Availability across interfaces

Procedures work identically over REST and Bolt (including the Neo4j Browser) — the same CALL dispatch serves both. Full-text and vector query procedures read from the local node’s copy of the index, so reads are served without a round trip to the leader.