APOC Functions¶
NornicDB includes 850+ APOC functions fully compatible with Neo4j's APOC library. All core functions are compiled into the binary for maximum performance, with optional plugin support for custom extensions.
Quick Start¶
Using APOC in Cypher¶
// Collection functions
MATCH (n:Person)
RETURN apoc.coll.sum(n.scores) AS total
// Text processing
RETURN apoc.text.join(['Hello', 'World'], ' ') AS greeting
// Graph algorithms
MATCH (n:Person)
RETURN apoc.algo.pageRank(n) AS rank
Configuration via Environment Variables¶
# Docker example - disable expensive algorithms, enable custom plugins
docker run \
-e NORNICDB_APOC_ALGO_ENABLED=false \
-e NORNICDB_PLUGINS_DIR=/plugins \
-v ./my-plugins:/plugins \
nornicdb/nornicdb
Available Function Categories¶
| Category | Functions | Description |
|---|---|---|
apoc.coll.* | 60+ | Collection operations (sum, avg, sort, filter, etc.) |
apoc.text.* | 50+ | Text processing (join, split, regex, Levenshtein, etc.) |
apoc.math.* | 50+ | Math operations (trig, stats, number theory) |
apoc.convert.* | 30+ | Type conversions (toInteger, toJson, etc.) |
apoc.map.* | 35+ | Map operations (merge, keys, values, flatten) |
apoc.date.* | 20+ | Date/time functions (parse, format, add) |
apoc.temporal.* | 40+ | Advanced date/time (timezone, duration, age) |
apoc.json.* | 25+ | JSON operations (path, validate, merge) |
apoc.util.* | 50+ | Utilities (MD5, SHA, UUID, compress) |
apoc.agg.* | 20+ | Aggregations (median, percentile, histogram) |
apoc.node.* | 40+ | Node operations (degree, labels, neighbors) |
apoc.nodes.* | 30+ | Batch node operations (link, group, filter) |
apoc.rel.* | 30+ | Relationship operations (properties, clone) |
apoc.path.* | 15+ | Path finding (shortestPath, allPaths) |
apoc.paths.* | 25+ | Advanced paths (k-shortest, disjoint, cycles) |
apoc.neighbors.* | 10+ | Neighbor traversal (BFS, DFS, atHop) |
apoc.algo.* | 15+ | Graph algorithms (PageRank, centrality) |
apoc.create.* | 25+ | Dynamic creation (virtual nodes, clone) |
apoc.atomic.* | 20+ | Atomic operations (add, subtract, locks) |
apoc.bitwise.* | 15+ | Bitwise operations (and, or, xor, shift) |
apoc.cypher.* | 20+ | Dynamic Cypher (run, parallel, parse) |
apoc.diff.* | 10+ | Diff operations (nodes, relationships, maps) |
apoc.export.* | 15+ | Export data (JSON, CSV, Cypher, GraphML) |
apoc.import.* | 15+ | Import data (JSON, CSV, GraphML, batch) |
apoc.graph.* | 15+ | Virtual graphs (from, merge, validate) |
apoc.hashing.* | 20+ | Hashing (MD5, SHA*, MurmurHash, xxHash) |
apoc.label.* | 15+ | Label operations (add, remove, merge) |
apoc.load.* | 30+ | Data loading (JSON, CSV, XML, JDBC, S3) |
apoc.lock.* | 15+ | Locking (nodes, relationships, deadlock) |
apoc.log.* | 25+ | Logging (info, debug, metrics, audit) |
apoc.merge.* | 20+ | Merge operations (nodes, rels, properties) |
apoc.meta.* | 30+ | Metadata (schema, stats, constraints) |
apoc.number.* | 40+ | Number formatting (roman, hex, base conversion) |
apoc.periodic.* | 10+ | Periodic execution (iterate, schedule) |
apoc.refactor.* | 25+ | Graph refactoring (merge, clone, normalize) |
apoc.schema.* | 25+ | Schema management (indexes, constraints) |
apoc.scoring.* | 25+ | Scoring/ranking (cosine, jaccard, TF-IDF) |
apoc.search.* | 30+ | Full-text search (fuzzy, regex, autocomplete) |
apoc.spatial.* | 25+ | Geographic functions (distance, bearing) |
apoc.stats.* | 30+ | Statistics (mean, median, correlation) |
apoc.trigger.* | 20+ | Trigger management (onCreate, onUpdate) |
apoc.warmup.* | 15+ | Database warmup (cache, indexes) |
apoc.xml.* | 25+ | XML processing (parse, query, transform) |
Configuration¶
Environment Variables¶
All APOC settings can be configured via environment variables (Docker/K8s friendly):
# Plugin directory for custom .so plugins
NORNICDB_PLUGINS_DIR=/opt/nornicdb/plugins
# Enable/disable function categories
NORNICDB_APOC_COLL_ENABLED=true # Collection functions
NORNICDB_APOC_TEXT_ENABLED=true # Text processing
NORNICDB_APOC_MATH_ENABLED=true # Math operations
NORNICDB_APOC_ALGO_ENABLED=false # Graph algorithms (disable if expensive)
NORNICDB_APOC_CREATE_ENABLED=false # Dynamic creation (disable for read-only)
# Security settings
NORNICDB_APOC_SECURITY_ALLOW_FILE_ACCESS=false
NORNICDB_APOC_SECURITY_MAX_COLLECTION_SIZE=100000
YAML Configuration¶
Alternatively, use a configuration file:
# /etc/nornicdb/apoc.yaml
# Plugin directory for custom .so plugins
plugins_dir: /opt/nornicdb/plugins
# Enable/disable categories
categories:
coll: true
text: true
math: true
algo: false # Disable expensive algorithms
create: false # Disable write operations
# Fine-grained function control (overrides categories)
functions:
"apoc.export.*": false # Disable all export
"apoc.import.*": false # Disable all import
"apoc.algo.pageRank": true # Re-enable specific algorithm
# Security
security:
allow_dynamic_creation: false
allow_file_access: false
max_collection_size: 10000
Docker Compose Example¶
version: '3.8'
services:
nornicdb:
image: nornicdb/nornicdb:latest
environment:
- NORNICDB_APOC_ALGO_ENABLED=false
- NORNICDB_APOC_CREATE_ENABLED=false
- NORNICDB_PLUGINS_DIR=/plugins
volumes:
- ./custom-plugins:/plugins
- ./data:/var/lib/nornicdb
ports:
- "7687:7687"
Custom Plugins¶
NornicDB supports loading custom functions from Go plugin files (.so).
Plugin Interface¶
Custom plugins must implement this interface:
type PluginInterface interface {
Name() string // Plugin name (e.g., "ml")
Version() string // Version (e.g., "1.0.0")
Functions() map[string]PluginFunction // Function definitions
}
type PluginFunction struct {
Handler interface{} // The function implementation
Description string // Documentation
Examples []string // Usage examples
}
Creating a Custom Plugin¶
Step 1: Create the plugin
// plugin_ml.go
package main
import (
"math"
"github.com/orneryd/nornicdb/apoc"
)
// Plugin must be exported
var Plugin MLPlugin
type MLPlugin struct{}
func (p MLPlugin) Name() string { return "ml" }
func (p MLPlugin) Version() string { return "1.0.0" }
func (p MLPlugin) Type() string { return "function" }
func (p MLPlugin) Functions() map[string]apoc.PluginFunction {
return map[string]apoc.PluginFunction{
"sigmoid": {
Handler: Sigmoid,
Description: "Sigmoid activation function",
Examples: []string{"apoc.ml.sigmoid(0) => 0.5"},
},
"relu": {
Handler: ReLU,
Description: "ReLU activation function",
Examples: []string{"apoc.ml.relu(-5) => 0"},
},
}
}
func Sigmoid(x float64) float64 {
return 1.0 / (1.0 + math.Exp(-x))
}
func ReLU(x float64) float64 {
if x < 0 {
return 0
}
return x
}
Step 2: Build as plugin
Step 3: Deploy
# Copy to plugins directory
cp apoc-ml.so /opt/nornicdb/plugins/
# Or mount in Docker
docker run -v ./apoc-ml.so:/plugins/apoc-ml.so \
-e NORNICDB_PLUGINS_DIR=/plugins \
nornicdb/nornicdb
Step 4: Use in Cypher
Auto-Detection¶
When NornicDB starts with NORNICDB_PLUGINS_DIR set, the unified plugin loader:
- Scans the directory for
*.sofiles - Validates each file has a
Pluginexport - Calls
Plugin.Type()to determine plugin type: "function"or"apoc"or""→ Function plugin (extends Cypher)"heimdall"→ Heimdall plugin (extends SLM subsystem)- Routes function plugins to Cypher executor
- Registers valid functions as
<plugin-name>.<function>(e.g.,apoc.ml.sigmoid) - Logs warnings for invalid plugins (doesn't prevent startup)
plugins/
├── apoc-ml.so ✅ Loaded → apoc.ml.sigmoid, apoc.ml.relu (function plugin)
├── apoc-kafka.so ✅ Loaded → apoc.kafka.produce, apoc.kafka.consume (function plugin)
├── random-lib.so ⚠️ Skipped (no Plugin export)
└── broken.so ⚠️ Skipped (invalid interface)
Note: For Heimdall subsystem plugins (SLM management), use NORNICDB_HEIMDALL_PLUGINS_DIR instead. See Plugin System for details.
Function Reference¶
Collection Functions (apoc.coll.*)¶
// Aggregation
apoc.coll.sum([1,2,3]) // → 6
apoc.coll.avg([1,2,3]) // → 2.0
apoc.coll.min([3,1,2]) // → 1
apoc.coll.max([3,1,2]) // → 3
// Transformation
apoc.coll.sort([3,1,2]) // → [1,2,3]
apoc.coll.reverse([1,2,3]) // → [3,2,1]
apoc.coll.flatten([[1,2],[3]]) // → [1,2,3]
// Set operations
apoc.coll.union([1,2], [2,3]) // → [1,2,3]
apoc.coll.intersection([1,2], [2,3]) // → [2]
apoc.coll.subtract([1,2,3], [2]) // → [1,3]
// Filtering
apoc.coll.contains([1,2,3], 2) // → true
apoc.coll.duplicates([1,2,2,3]) // → [2]
apoc.coll.frequencies([1,2,2,3]) // → {1:1, 2:2, 3:1}
Text Functions (apoc.text.*)¶
// Basic
apoc.text.join(['a','b'], '-') // → "a-b"
apoc.text.split('a-b', '-') // → ["a", "b"]
apoc.text.replace('hello', 'l', 'L') // → "heLLo"
// Case conversion
apoc.text.capitalize('hello') // → "Hello"
apoc.text.camelCase('hello_world') // → "helloWorld"
apoc.text.snakeCase('helloWorld') // → "hello_world"
// String similarity (full algorithms, not placeholders)
apoc.text.distance('hello', 'helo') // → 1 (Levenshtein)
apoc.text.jaroWinklerDistance('hello', 'helo') // → 0.96
apoc.text.hammingDistance('hello', 'hallo') // → 1
// Phonetic
apoc.text.phonetic('Robert') // → "R163" (Soundex)
Math Functions (apoc.math.*)¶
// Basic
apoc.math.round(3.7) // → 4
apoc.math.ceil(3.2) // → 4
apoc.math.floor(3.8) // → 3
apoc.math.abs(-5) // → 5
// Statistics
apoc.math.mean([1,2,3,4,5]) // → 3.0
apoc.math.median([1,2,3,4,5]) // → 3.0
apoc.math.stdDev([1,2,3,4,5]) // → 1.414...
apoc.math.percentile([1,2,3,4,5], 0.5) // → 3.0
// Number theory
apoc.math.gcd(12, 8) // → 4
apoc.math.lcm(12, 8) // → 24
apoc.math.factorial(5) // → 120
apoc.math.isPrime(17) // → true
Graph Algorithm Functions (apoc.algo.*)¶
// Centrality
MATCH (n:Person)
RETURN n.name, apoc.algo.pageRank(n) AS rank
ORDER BY rank DESC
MATCH (n:Person)
RETURN n.name, apoc.algo.betweennessCentrality(n) AS centrality
// Pathfinding
MATCH (start:Person {name:'Alice'}), (end:Person {name:'Bob'})
RETURN apoc.algo.dijkstra(start, end, 'KNOWS', 'weight')
// Community detection
MATCH (n:Person)
RETURN apoc.algo.community(n) AS communityId
Atomic Operations (apoc.atomic.*)¶
// Atomic updates
MATCH (n:Counter {id: 'visits'})
CALL apoc.atomic.add(n, 'count', 1)
RETURN n.count
// Atomic list operations
MATCH (n:User {id: 123})
CALL apoc.atomic.insert(n, 'tags', 0, 'featured')
// Compare and swap
MATCH (n:Lock {resource: 'db'})
CALL apoc.atomic.compareAndSwap(n, 'owner', null, 'process-123')
Bitwise Operations (apoc.bitwise.*)¶
// Basic operations
RETURN apoc.bitwise.op(12, '&', 10) AS result // → 8
RETURN apoc.bitwise.and(12, 10, 8) AS result // → 8
RETURN apoc.bitwise.or(4, 2, 1) AS result // → 7
// Bit manipulation
RETURN apoc.bitwise.setBit(0, 3) AS result // → 8
RETURN apoc.bitwise.testBit(8, 3) AS result // → true
RETURN apoc.bitwise.countBits(15) AS result // → 4
Dynamic Cypher (apoc.cypher.*)¶
// Run dynamic queries
CALL apoc.cypher.run('MATCH (n:Person) WHERE n.age > $age RETURN n', {age: 30})
YIELD value
RETURN value.n
// Run multiple queries
CALL apoc.cypher.runMany('
CREATE (n:Person {name: $name});
MATCH (n:Person) RETURN count(n);
', {name: 'Alice'})
// Parallel execution
CALL apoc.cypher.parallel(['query1', 'query2'], {})
Diff Operations (apoc.diff.*)¶
// Compare nodes
MATCH (n1:Person {id: 1}), (n2:Person {id: 2})
RETURN apoc.diff.nodes(n1, n2) AS differences
// Compare maps
RETURN apoc.diff.maps(
{name: 'Alice', age: 30},
{name: 'Alice', age: 31}
) AS changes
// → {changed: {age: {old: 30, new: 31}}}
Export Functions (apoc.export.*)¶
// Export to JSON
MATCH (n:Person)
CALL apoc.export.json.query('MATCH (n:Person) RETURN n', '/tmp/people.json', {})
// Export to CSV
MATCH (n:Person)
CALL apoc.export.csv.all('/tmp/graph.csv', {})
// Export to Cypher
CALL apoc.export.cypher.all('/tmp/backup.cypher', {format: 'cypher-shell'})
Import Functions (apoc.import.*)¶
// Import JSON
CALL apoc.import.json('/data/people.json')
YIELD node
RETURN node
// Import CSV
CALL apoc.import.csv('/data/data.csv', {delimiter: ',', header: true})
// Batch import
CALL apoc.import.batch([
{labels: ['Person'], props: {name: 'Alice'}},
{labels: ['Person'], props: {name: 'Bob'}}
], 1000)
Hashing Functions (apoc.hashing.*)¶
// Cryptographic hashes
RETURN apoc.hashing.md5('hello') AS hash
RETURN apoc.hashing.sha256('password') AS hash
RETURN apoc.hashing.sha512('data') AS hash
// Fast hashes
RETURN apoc.hashing.murmur3('key') AS hash
RETURN apoc.hashing.xxhash('data') AS hash
// Consistent hashing
RETURN apoc.hashing.consistentHash('user-123', 10) AS bucket
Label Operations (apoc.label.*)¶
// Add labels
MATCH (n:Person {id: 123})
CALL apoc.label.add(n, ['Employee', 'Manager'])
// Remove labels
MATCH (n:Person)
CALL apoc.label.remove(n, ['Temporary'])
// Check labels
MATCH (n)
WHERE apoc.label.has(n, 'Person')
RETURN n
Load Functions (apoc.load.*)¶
// Load JSON from URL
CALL apoc.load.json('https://api.example.com/data')
YIELD value
RETURN value
// Load CSV
CALL apoc.load.csv('/data/file.csv', {header: true})
YIELD map
CREATE (n:Person) SET n = map
// Load from S3
CALL apoc.load.s3('s3://bucket/data.json', {region: 'us-east-1'})
// Load from database
CALL apoc.load.jdbc('jdbc:postgresql://localhost/db', 'SELECT * FROM users')
Lock Functions (apoc.lock.*)¶
// Lock nodes
MATCH (n:Resource {id: 'db'})
CALL apoc.lock.nodes([n])
// ... perform operations ...
CALL apoc.lock.unlock([n])
// Read/write locks
CALL apoc.lock.read([node1, node2])
CALL apoc.lock.write([node3])
// Detect deadlocks
CALL apoc.lock.detectDeadlock()
YIELD deadlock
RETURN deadlock
Logging Functions (apoc.log.*)¶
// Basic logging
CALL apoc.log.info('Processing started', {count: 100})
CALL apoc.log.debug('Variable value', {var: value})
CALL apoc.log.warn('Deprecated function', {function: 'old'})
CALL apoc.log.error('Operation failed', {error: message})
// Performance logging
WITH apoc.log.timer('query') AS stopTimer
MATCH (n:Person) WHERE n.age > 30
WITH collect(n) AS results, stopTimer
CALL stopTimer()
RETURN results
// Metrics
CALL apoc.log.metrics('query_time', 150, 'ms')
Merge Operations (apoc.merge.*)¶
// Merge nodes
CALL apoc.merge.node(['Person'], {email: 'alice@example.com'},
{created: timestamp()}, {updated: timestamp()})
YIELD node
RETURN node
// Merge relationships
MATCH (a:Person {id: 1}), (b:Person {id: 2})
CALL apoc.merge.relationship(a, 'KNOWS', {}, {since: 2020}, {}, b)
YIELD rel
RETURN rel
// Deep merge properties
MATCH (n:Person {id: 123})
CALL apoc.merge.deepMerge(n, {address: {city: 'NYC', zip: '10001'}})
Metadata Functions (apoc.meta.*)¶
// Get schema
CALL apoc.meta.schema()
YIELD value
RETURN value
// Get statistics
CALL apoc.meta.stats()
YIELD labelCount, relTypeCount
RETURN labelCount, relTypeCount
// Get node type properties
CALL apoc.meta.nodeTypeProperties('Person')
YIELD propertyName, propertyType
RETURN propertyName, propertyType
Neighbor Traversal (apoc.neighbors.*)¶
// Get neighbors at specific distance
MATCH (n:Person {name: 'Alice'})
RETURN apoc.neighbors.atHop(n, 'KNOWS', 2) AS secondDegree
// Get all neighbors up to distance
MATCH (n:Person {name: 'Alice'})
RETURN apoc.neighbors.toHop(n, 'KNOWS', 3) AS network
// BFS traversal
MATCH (n:Person {name: 'Alice'})
CALL apoc.neighbors.bfs(n, 'KNOWS', 5)
YIELD node
RETURN node
Batch Node Operations (apoc.nodes.*)¶
// Link nodes in sequence
MATCH (n:Step)
WITH collect(n) AS steps
CALL apoc.nodes.link(steps, 'NEXT')
YIELD rel
RETURN rel
// Group nodes by property
MATCH (n:Person)
WITH collect(n) AS people
RETURN apoc.nodes.group(people, 'department') AS grouped
// Filter nodes
MATCH (n:Person)
WITH collect(n) AS people
RETURN apoc.nodes.filter(people, function(n) {
RETURN n.age > 18
}) AS adults
Number Functions (apoc.number.*)¶
// Format numbers
RETURN apoc.number.format(1234.5678, '#,##0.00') AS formatted
// → "1,234.57"
// Roman numerals
RETURN apoc.number.romanize(14) AS roman // → "XIV"
RETURN apoc.number.arabize('XIV') AS number // → 14
// Base conversion
RETURN apoc.number.toHex(255) AS hex // → "FF"
RETURN apoc.number.fromHex('FF') AS decimal // → 255
RETURN apoc.number.toBinary(10) AS binary // → "1010"
Advanced Path Operations (apoc.paths.*)¶
// Find all paths
MATCH (start:Person {name: 'Alice'}), (end:Person {name: 'Bob'})
CALL apoc.paths.all(start, end, 'KNOWS', 5)
YIELD path
RETURN path
// K-shortest paths
MATCH (start:Person), (end:Person)
CALL apoc.paths.kShortest(start, end, 'KNOWS', 10, 3)
YIELD path
RETURN path
// Node-disjoint paths
CALL apoc.paths.disjoint(start, end, 'KNOWS', 10, 2)
YIELD path
RETURN path
Periodic Execution (apoc.periodic.*)¶
// Batch processing
CALL apoc.periodic.iterate(
'MATCH (n:Person) RETURN n',
'SET n.processed = true',
{batchSize: 1000, parallel: true}
)
// Scheduled execution
CALL apoc.periodic.schedule('cleanup',
'MATCH (n:Temp) DELETE n',
60) // Every 60 seconds
// Commit in batches
CALL apoc.periodic.commit(
'MATCH (n:Person) WHERE n.migrated IS NULL
WITH n LIMIT $limit
SET n.migrated = true
RETURN count(*)',
{limit: 1000}
)
Graph Refactoring (apoc.refactor.*)¶
// Merge nodes
MATCH (n1:Person {id: 1}), (n2:Person {id: 2})
CALL apoc.refactor.mergeNodes([n1, n2], {properties: 'combine'})
YIELD node
RETURN node
// Clone subgraph
MATCH path = (n:Person)-[r:KNOWS]->(m:Person)
WITH collect(n) + collect(m) AS nodes, collect(r) AS rels
CALL apoc.refactor.cloneSubgraph(nodes, rels)
YIELD nodes AS newNodes, relationships AS newRels
RETURN newNodes, newRels
// Normalize data
MATCH (n:Person)
CALL apoc.refactor.normalize(n, 'city', 'City', 'LIVES_IN')
Relationship Operations (apoc.rel.*)¶
// Get relationship properties
MATCH ()-[r:KNOWS]->()
RETURN apoc.rel.type(r), apoc.rel.properties(r)
// Clone relationship
MATCH ()-[r:KNOWS]->()
WITH r LIMIT 1
CALL apoc.rel.clone(r)
YIELD rel
RETURN rel
// Get relationship weight
MATCH ()-[r:KNOWS]->()
RETURN apoc.rel.weight(r, 'strength', 1.0) AS weight
Schema Management (apoc.schema.*)¶
// Create index
CALL apoc.schema.index.create('Person', ['name'])
// Create constraint
CALL apoc.schema.constraint.create('Person', ['email'], 'UNIQUE')
// List all indexes
CALL apoc.schema.node.indexes()
YIELD name, label, properties
RETURN name, label, properties
// Validate schema
CALL apoc.schema.validate()
YIELD valid, errors
RETURN valid, errors
Scoring Functions (apoc.scoring.*)¶
// Cosine similarity
RETURN apoc.scoring.cosine([1,2,3], [4,5,6]) AS similarity
// Jaccard similarity
RETURN apoc.scoring.jaccard([1,2,3], [2,3,4]) AS similarity
// TF-IDF
RETURN apoc.scoring.tfidf('hello', 'hello world hello', 100, 30) AS score
// Pearson correlation
RETURN apoc.scoring.pearson([1,2,3,4], [2,4,6,8]) AS correlation
Search Functions (apoc.search.*)¶
// Full-text search
CALL apoc.search.fullText('Person', 'name', 'Alice Bob')
YIELD node
RETURN node
// Fuzzy search
CALL apoc.search.fuzzy('Person', 'name', 'Alise', 2)
YIELD node
RETURN node
// Regex search
CALL apoc.search.regex('Person', 'email', '.*@example\\.com')
YIELD node
RETURN node
// Autocomplete
CALL apoc.search.autocomplete('Person', 'name', 'Al')
YIELD suggestion
RETURN suggestion
Spatial Functions (apoc.spatial.*)¶
// Calculate distance
WITH {latitude: 40.7128, longitude: -74.0060} AS nyc,
{latitude: 51.5074, longitude: -0.1278} AS london
RETURN apoc.spatial.distance(nyc, london) AS distanceKm
// Find nearest points
MATCH (p:Place)
WITH {latitude: 40.7128, longitude: -74.0060} AS target,
collect(p) AS places
RETURN apoc.spatial.kNearest(target, places, 5) AS nearest
// Check if within bounding box
RETURN apoc.spatial.within(
{latitude: 40.7128, longitude: -74.0060},
{minLat: 40, maxLat: 41, minLon: -75, maxLon: -73}
) AS isWithin
Statistics Functions (apoc.stats.*)¶
// Basic statistics
WITH [1,2,3,4,5,6,7,8,9,10] AS values
RETURN apoc.stats.mean(values) AS mean,
apoc.stats.median(values) AS median,
apoc.stats.stdDev(values) AS stdDev
// Correlation
WITH [1,2,3,4,5] AS x, [2,4,6,8,10] AS y
RETURN apoc.stats.correlation(x, y) AS correlation
// Percentiles
WITH [1,2,3,4,5,6,7,8,9,10] AS values
RETURN apoc.stats.percentile(values, 0.95) AS p95
Temporal Functions (apoc.temporal.*)¶
// Format dates
RETURN apoc.temporal.format(datetime(), 'yyyy-MM-dd HH:mm:ss') AS formatted
// Parse dates
RETURN apoc.temporal.parse('2024-01-15', 'yyyy-MM-dd') AS date
// Date arithmetic
RETURN apoc.temporal.add(datetime(), 7, 'days') AS nextWeek
RETURN apoc.temporal.subtract(datetime(), 1, 'month') AS lastMonth
// Calculate age
WITH date('1990-01-15') AS birthdate
RETURN apoc.temporal.age(birthdate) AS age
// Timezone conversion
RETURN apoc.temporal.timezone(datetime(), 'America/New_York') AS nyTime
Trigger Management (apoc.trigger.*)¶
// Add trigger
CALL apoc.trigger.add('onPersonCreate',
'MATCH (n:Person) SET n.created = timestamp()',
{phase: 'after'}
)
// List triggers
CALL apoc.trigger.list()
YIELD name, statement, enabled
RETURN name, statement, enabled
// Pause/resume triggers
CALL apoc.trigger.pause('onPersonCreate')
CALL apoc.trigger.resume('onPersonCreate')
// Remove trigger
CALL apoc.trigger.remove('onPersonCreate')
Warmup Functions (apoc.warmup.*)¶
// Warm up entire database
CALL apoc.warmup.run()
YIELD nodesLoaded, relationshipsLoaded, timeTaken
RETURN nodesLoaded, relationshipsLoaded, timeTaken
// Warm up specific labels
CALL apoc.warmup.nodes(['Person', 'Company'])
// Warm up indexes
CALL apoc.warmup.indexes()
// Warm up subgraph
MATCH (n:Person {id: 123})
CALL apoc.warmup.subgraph(n, 3)
XML Functions (apoc.xml.*)¶
// Parse XML
WITH '<root><item>value</item></root>' AS xml
RETURN apoc.xml.parse(xml) AS parsed
// Query XML
WITH apoc.xml.parse('<root><item id="1">A</item></root>') AS doc
RETURN apoc.xml.query(doc, '//item[@id="1"]') AS items
// Convert to JSON
WITH '<root><item>value</item></root>' AS xml
RETURN apoc.xml.toJson(xml) AS json
Security Considerations¶
Production Recommendations¶
# Disable write operations
NORNICDB_APOC_CREATE_ENABLED=false
# Disable file access
NORNICDB_APOC_SECURITY_ALLOW_FILE_ACCESS=false
# Limit collection sizes to prevent OOM
NORNICDB_APOC_SECURITY_MAX_COLLECTION_SIZE=10000
# Disable expensive algorithms if not needed
NORNICDB_APOC_ALGO_ENABLED=false
Plugin Security¶
- Only load plugins from trusted sources
- Plugins run with full NornicDB permissions
- Use file permissions to restrict plugin directory:
Troubleshooting¶
Plugin Not Loading¶
Cause: The .so file doesn't have a var Plugin export.
Fix: Ensure your plugin exports var Plugin YourPluginType.
Function Not Found¶
Possible causes: 1. Plugin not loaded (check NORNICDB_PLUGINS_DIR) 2. Function category disabled (check NORNICDB_APOC_<CATEGORY>_ENABLED) 3. Function specifically disabled in config
Platform Compatibility¶
Go plugins are platform-specific. A .so built on Linux x86_64 won't work on: - macOS (use .dylib) - Windows (use .dll) - Linux ARM
Build plugins on the same platform/architecture as your NornicDB deployment.
Architecture¶
┌─────────────────────────────────────────────────────────┐
│ NornicDB Binary │
├─────────────────────────────────────────────────────────┤
│ Core APOC Functions (450+ compiled in) │
│ ├── apoc.coll.* ├── apoc.text.* ├── apoc.math.* │
│ ├── apoc.convert.* ├── apoc.map.* ├── apoc.date.* │
│ ├── apoc.json.* ├── apoc.util.* ├── apoc.agg.* │
│ ├── apoc.node.* ├── apoc.path.* └── apoc.algo.* │
├─────────────────────────────────────────────────────────┤
│ Plugin Loader (auto-loads from NORNICDB_PLUGINS_DIR)│
│ └── Scans *.so → Validates interface → Registers funcs │
├─────────────────────────────────────────────────────────┤
│ Configuration (env vars or YAML) │
│ └── Controls which functions are enabled │
└─────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ Optional Plugins Directory (/opt/nornicdb/plugins) │
│ ├── apoc-ml.so → apoc.ml.* │
│ ├── apoc-kafka.so → apoc.kafka.* │
│ └── custom-*.so → apoc.custom.* │
└─────────────────────────────────────────────────────────┘
Migration from Neo4j APOC¶
NornicDB's APOC implementation is designed for compatibility:
| Neo4j APOC | NornicDB | Notes |
|---|---|---|
apoc.coll.* | ✅ Same | Full compatibility - all collection functions |
apoc.text.* | ✅ Same | Full compatibility - text processing |
apoc.math.* | ✅ Same | Full compatibility - math operations |
apoc.algo.* | ✅ Same | Real algorithms (PageRank, centrality, etc.) |
apoc.atomic.* | ✅ Same | Atomic operations with locking |
apoc.bitwise.* | ✅ Same | Bitwise operations |
apoc.cypher.* | ✅ Same | Dynamic Cypher execution |
apoc.diff.* | ✅ Same | Diff operations |
apoc.export.* | ✅ Same | Export to JSON, CSV, Cypher, GraphML |
apoc.import.* | ✅ Same | Import from multiple formats |
apoc.graph.* | ✅ Same | Virtual graph operations |
apoc.hashing.* | ✅ Same | Multiple hash algorithms |
apoc.label.* | ✅ Same | Label operations |
apoc.load.* | ✅ Same | Load from JSON, CSV, XML, JDBC, S3, etc. |
apoc.lock.* | ✅ Same | Locking mechanisms |
apoc.log.* | ✅ Same | Logging functions |
apoc.merge.* | ✅ Same | Merge operations |
apoc.meta.* | ✅ Same | Metadata functions |
apoc.neighbors.* | ✅ Same | Neighbor traversal |
apoc.nodes.* | ✅ Same | Batch node operations |
apoc.number.* | ✅ Same | Number formatting and conversion |
apoc.paths.* | ✅ Same | Advanced path operations |
apoc.periodic.* | ✅ Same | Periodic execution and batch processing |
apoc.refactor.* | ✅ Same | Graph refactoring |
apoc.rel.* | ✅ Same | Relationship operations |
apoc.schema.* | ✅ Same | Schema management |
apoc.scoring.* | ✅ Same | Scoring and similarity functions |
apoc.search.* | ✅ Same | Full-text search |
apoc.spatial.* | ✅ Same | Geographic functions |
apoc.stats.* | ✅ Same | Statistical functions |
apoc.temporal.* | ✅ Same | Advanced date/time operations |
apoc.trigger.* | ✅ Same | Trigger management |
apoc.warmup.* | ✅ Same | Database warmup |
apoc.xml.* | ✅ Same | XML processing |
Migration Notes: - Most queries work without modification - All core APOC functions are implemented - File operations respect security settings - Plugin system allows custom extensions - Performance characteristics may differ (often faster due to native Go implementation)
Test your specific APOC usage when migrating, particularly: - File I/O operations (check security settings) - Custom procedures (may need to be rewritten as plugins) - Performance-sensitive queries (benchmark in your environment)