User Functions
ArcadeDB can be extended with user-defined functions written in SQL, JavaScript, OpenCypher, or Java. Functions defined in any language are callable from ALL query languages (SQL, Cypher, Gremlin, GraphQL, MongoDB).
Overview
User functions provide a powerful way to encapsulate business logic and reuse it across different query languages:
-
SQL Functions: Use SQL queries as function bodies
-
JavaScript Functions: Use JavaScript for complex calculations
-
OpenCypher Functions: Use Cypher queries for graph operations
-
Java Functions: Use Java for maximum performance
|
Universal Function Registry Functions are stored in a global registry and are accessible from any query language:
|
Database’s Functions
Look at the Database Interface page for built-in database functions.
Defining User Functions
User functions can be defined via SQL using the following command:
DEFINE FUNCTION <library>.<name> "<body>" [PARAMETERS [<parameter>,*]] LANGUAGE <language>
-
<library>- A namespace to group related functions -
<name>- The function’s name -
<body>- The function’s body as a string in the chosen language’s syntax -
[<parameter>,*]- A list of parameter identifiers used in the function body. Omit thePARAMETERS []block for functions without parameters -
<language>- One of:sql,js(JavaScript), oropencypher(aliascypher). Java is not a validLANGUAGEvalue: Java functions are registered programmatically (see User Functions in Java below)
SQL Language Functions
SQL functions execute SQL queries and return the result of the first row’s projection.
The return value in a SQL function is determined by the projection named 'result' or the first column if only one column is returned.
|
Examples
// Simple arithmetic function
DEFINE FUNCTION math.add "SELECT :a + :b AS result" PARAMETERS [a,b] LANGUAGE sql;
SELECT `math.add`(10, 5) AS sum; // Returns 15
// Function returning a string
DEFINE FUNCTION the.answer 'SELECT "forty-two" AS result' LANGUAGE sql;
SELECT `the.answer`(); // Returns "forty-two"
// Function with no parameters
DEFINE FUNCTION utils.currentYear 'SELECT date_format(sysdate(), "yyyy") AS result' LANGUAGE sql;
SELECT `utils.currentYear`();
JavaScript Language Functions
JavaScript functions use GraalVM’s JavaScript engine for complex calculations and data transformations.
Examples
// Fused multiply-add function
DEFINE FUNCTION my.fma 'return a + b * c' PARAMETERS [a,b,c] LANGUAGE js;
SELECT `my.fma`(1, 2, 3); // Returns 7
// String manipulation
DEFINE FUNCTION text.greet 'return "Hello, " + name + "!"' PARAMETERS [name] LANGUAGE js;
SELECT `text.greet`('World'); // Returns "Hello, World!"
// Complex JSON processing
DEFINE FUNCTION json.extract 'return JSON.parse(data).users.length' PARAMETERS [data] LANGUAGE js;
SELECT `json.extract`('{"users":[1,2,3]}'); // Returns 3
OpenCypher Language Functions
OpenCypher functions execute Cypher queries, perfect for graph traversals and pattern matching.
You can use either opencypher or cypher as the language identifier (they are aliases).
|
Examples
// Simple calculation using Cypher
DEFINE FUNCTION cypher.double "RETURN $x * 2" PARAMETERS [x] LANGUAGE opencypher;
SELECT `cypher.double`(5); // Returns 10
// Graph query function
DEFINE FUNCTION graph.countNeighbors
"MATCH (n) WHERE id(n) = $nodeId MATCH (n)-[]->() RETURN count(*) AS cnt"
PARAMETERS [nodeId] LANGUAGE cypher;
// Use in SQL query
SELECT `graph.countNeighbors`('#1:0') AS neighbors;
// Use in Cypher query
MATCH (n:Person) RETURN n.name, graph.countNeighbors(id(n)) AS friendCount;
// Complex graph pattern function
DEFINE FUNCTION graph.findCommonFriends
"MATCH (a)-[:KNOWS]->(common)<-[:KNOWS]-(b)
WHERE id(a) = $id1 AND id(b) = $id2
RETURN count(DISTINCT common) AS cnt"
PARAMETERS [id1, id2] LANGUAGE opencypher;
Calling User Functions
Functions can be called from any supported query language:
| Language | Syntax | Example |
|---|---|---|
SQL |
|
|
Cypher |
|
|
Cypher CALL |
|
|
Gremlin |
Standard function call |
Access via SQL bridge |
GraphQL |
In query fields |
Access via SQL bridge |
Cross-Language Function Calls
One of ArcadeDB’s unique features is the ability to define a function in one language and call it from any other:
// Define in SQL
DEFINE FUNCTION sql.concat "SELECT :a || :b" PARAMETERS [a,b] LANGUAGE sql;
// Define in JavaScript
DEFINE FUNCTION js.power "return Math.pow(x, y)" PARAMETERS [x,y] LANGUAGE js;
// Define in Cypher
DEFINE FUNCTION cypher.shortestPath
"MATCH path=shortestPath((a)-[*]-(b))
WHERE id(a)=$from AND id(b)=$to
RETURN length(path)"
PARAMETERS [from, to] LANGUAGE opencypher;
// Call all from Cypher
RETURN sql.concat('Hello', ' World') AS greeting,
js.power(2, 10) AS power,
cypher.shortestPath('#1:0', '#1:100') AS pathLen;
// Call all from SQL
SELECT `sql.concat`('foo', 'bar') AS str,
`js.power`(3, 3) AS cube,
`cypher.shortestPath`('#1:0', '#1:100') AS dist;
User Functions in Java
Java functions provide maximum performance and access to the full Java ecosystem. Unlike the SQL, JavaScript and OpenCypher functions described above, Java functions cannot be created with DEFINE FUNCTION … LANGUAGE java: they must be registered programmatically through the Java API. There is no auto-discovery (no ServiceLoader, no annotation or classpath scan) for user functions: registration is always an explicit API call.
|
Where does the JAR go, and when is the function registered?
|
There are two independent ways to expose Java code as functions.
Option A: Register a Java class or method as a function library
A function library registered on the schema is callable from all query languages, exactly like the SQL/JavaScript/Cypher functions above. Use JavaClassFunctionLibraryDefinition to expose every public method of a class, or JavaMethodFunctionLibraryDefinition to expose a single method. The library name becomes the namespace and each method name becomes a function in it.
Given this class deployed in a JAR under lib/:
package com.example;
public class MathFunctions {
// Instance method: invoked on a new instance created via the no-arg constructor
public int sum(final int a, final int b) {
return a + b;
}
// Static method: invoked directly, no instance needed
public static double max(final double a, final double b) {
return Math.max(a, b);
}
}
Register it on the database schema (embedded usage shown):
// Register the whole class as a library named "math".
// Every public method (sum, max, ...) becomes a function in that library.
database.getSchema().registerFunctionLibrary(
new JavaClassFunctionLibraryDefinition("math", "com.example.MathFunctions"));
// ...or pass the Class directly:
database.getSchema().registerFunctionLibrary(
new JavaClassFunctionLibraryDefinition("math", com.example.MathFunctions.class));
// ...or register a single method as a one-function library:
database.getSchema().registerFunctionLibrary(
new JavaMethodFunctionLibraryDefinition("math",
com.example.MathFunctions.class.getMethod("sum", Integer.TYPE, Integer.TYPE)));
A library name can be registered only once. Re-registering the same name throws IllegalArgumentException. Remove it first with database.getSchema().unregisterFunctionLibrary("math").
|
Once registered, call the function from any query language using the library.name form:
// Direct invocation through the API
Object r = database.getSchema().getFunction("math", "sum").execute(20, 7); // 27
// From SQL (backtick-quote the library.function reference)
database.command("sql", "SELECT `math.sum`(20, 7) AS result");
// From Cypher
database.query("opencypher", "RETURN math.sum(20, 7) AS result");
Option B: The dedicated "java" query engine
The java query engine invokes a Java method directly as a command, without going through the schema. For security, every class or method must be added to an allow-list with registerFunctions(…) before it can be called; calling anything that was not registered throws a SecurityException.
// Allow-list a single method (Class::method) or a whole class
database.getQueryEngine("java").registerFunctions("com.example.MathFunctions::sum");
database.getQueryEngine("java").registerFunctions("com.example.MathFunctions"); // all methods
// Invoke it (positional parameters follow the command string)
ResultSet result = database.command("java", "com.example.MathFunctions::sum", 5, 3);
int value = (int) result.next().getProperty("value"); // 8
// Remove the allow-list when done
database.getQueryEngine("java").unregisterFunctions();
The java engine supports only command(…) (not query(…)) and positional parameters; it matches overloaded methods by argument count and type.
Registering Java functions on a standalone server
On a standalone server there is no application code to call registerFunctionLibrary(…), so wrap the registration in a Server Plugin. Build a JAR containing both the function class and a plugin like the one below, place it in lib/plugins/, and enable it.
package com.example;
import com.arcadedb.ContextConfiguration;
import com.arcadedb.function.java.JavaClassFunctionLibraryDefinition;
import com.arcadedb.server.ArcadeDBServer;
import com.arcadedb.server.ServerPlugin;
public class MathFunctionsPlugin implements ServerPlugin {
private ArcadeDBServer server;
@Override
public void configure(final ArcadeDBServer arcadeDBServer, final ContextConfiguration configuration) {
this.server = arcadeDBServer;
}
@Override
// Run after the databases are open so they are available for registration
public PluginInstallationPriority getInstallationPriority() {
return PluginInstallationPriority.AFTER_DATABASES_OPEN;
}
@Override
public void startService() {
// Register the library on every database that needs it
server.getDatabase("mydb").getSchema().registerFunctionLibrary(
new JavaClassFunctionLibraryDefinition("math", "com.example.MathFunctions"));
}
}
Enable the plugin with the arcadedb.server.plugins setting, using the <pluginName>:<pluginFullClassName> format (comma-separate multiple plugins):
-Darcadedb.server.plugins=Math:com.example.MathFunctionsPlugin
After the server starts, the functions are available from SQL, Cypher and the other query languages (for example `), including over the HTTP/REST API.SELECT `math.sum(20, 7)
Best Practices
-
Choose the Right Language:
-
Use SQL for data transformations and aggregations
-
Use JavaScript for complex business logic and calculations
-
Use OpenCypher for graph traversals and pattern matching
-
Use Java for maximum performance
-
-
Namespace Organization: Group related functions under the same library namespace [source,sql] ---- DEFINE FUNCTION math.sum … DEFINE FUNCTION math.avg … DEFINE FUNCTION utils.formatDate … DEFINE FUNCTION graph.shortestPath … ----
-
Function Naming: Use descriptive names that indicate what the function does
-
Performance Considerations:
-
User functions are evaluated for each row/result
-
For heavy operations, consider caching or materialized views
-
Java functions provide the best performance
-
-
Error Handling: Functions should handle null inputs gracefully
Advantages Over Neo4j
Unlike Neo4j which only supports Java-based user-defined procedures requiring compilation and deployment:
✅ Dynamic Definition: Define functions at runtime without restart
✅ 4 Languages: SQL, JavaScript, OpenCypher, and Java
✅ Universal Access: Call from any query language
✅ No Compilation: Interpreted languages work immediately
✅ Easy Management: Simple SQL commands to define/delete functions