APOC Functions

December 6, 2025 · View on GitHub

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

CategoryFunctionsDescription
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

go build -buildmode=plugin -o apoc-ml.so plugin_ml.go

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

RETURN apoc.ml.sigmoid(0.5) AS activation
// Returns: 0.6224593312018546

Auto-Detection

When NornicDB starts with NORNICDB_PLUGINS_DIR set, the unified plugin loader:

  1. Scans the directory for *.so files
  2. Validates each file has a Plugin export
  3. Calls Plugin.Type() to determine plugin type:
    • "function" or "apoc" or "" → Function plugin (extends Cypher)
    • "heimdall" → Heimdall plugin (extends SLM subsystem)
  4. Routes function plugins to Cypher executor
  5. Registers valid functions as <plugin-name>.<function> (e.g., apoc.ml.sigmoid)
  6. 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:
chmod 755 /opt/nornicdb/plugins
chown root:root /opt/nornicdb/plugins/*.so

Troubleshooting

Plugin Not Loading

Warning: failed to load plugins from /plugins: plugin does not export 'Plugin'

Cause: The .so file doesn't have a var Plugin export.

Fix: Ensure your plugin exports var Plugin YourPluginType.

Function Not Found

RETURN apoc.custom.myFunc('test')
// Error: function apoc.custom.myFunc 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 APOCNornicDBNotes
apoc.coll.*✅ SameFull compatibility - all collection functions
apoc.text.*✅ SameFull compatibility - text processing
apoc.math.*✅ SameFull compatibility - math operations
apoc.algo.*✅ SameReal algorithms (PageRank, centrality, etc.)
apoc.atomic.*✅ SameAtomic operations with locking
apoc.bitwise.*✅ SameBitwise operations
apoc.cypher.*✅ SameDynamic Cypher execution
apoc.diff.*✅ SameDiff operations
apoc.export.*✅ SameExport to JSON, CSV, Cypher, GraphML
apoc.import.*✅ SameImport from multiple formats
apoc.graph.*✅ SameVirtual graph operations
apoc.hashing.*✅ SameMultiple hash algorithms
apoc.label.*✅ SameLabel operations
apoc.load.*✅ SameLoad from JSON, CSV, XML, JDBC, S3, etc.
apoc.lock.*✅ SameLocking mechanisms
apoc.log.*✅ SameLogging functions
apoc.merge.*✅ SameMerge operations
apoc.meta.*✅ SameMetadata functions
apoc.neighbors.*✅ SameNeighbor traversal
apoc.nodes.*✅ SameBatch node operations
apoc.number.*✅ SameNumber formatting and conversion
apoc.paths.*✅ SameAdvanced path operations
apoc.periodic.*✅ SamePeriodic execution and batch processing
apoc.refactor.*✅ SameGraph refactoring
apoc.rel.*✅ SameRelationship operations
apoc.schema.*✅ SameSchema management
apoc.scoring.*✅ SameScoring and similarity functions
apoc.search.*✅ SameFull-text search
apoc.spatial.*✅ SameGeographic functions
apoc.stats.*✅ SameStatistical functions
apoc.temporal.*✅ SameAdvanced date/time operations
apoc.trigger.*✅ SameTrigger management
apoc.warmup.*✅ SameDatabase warmup
apoc.xml.*✅ SameXML 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)