Database API
The two core classes of the embedded Java API are:
-
DatabaseFactory— entry point that creates and opens databases. -
Database— main handle for transactions, queries, schema, and CRUD operations.
DatabaseFactory Class
It’s the entry point class that allows to create and open a database.
A DatabaseFactory object doesn’t keep any state and its only goal is creating a Database instance.
Never open the same database directory from two processes at once (for example, an embedded tool and a running ArcadeDB Server). Doing so can corrupt the database. If a server already has a database open, connect to it remotely instead of opening it with DatabaseFactory.
|
Example:
DatabaseFactory factory = new DatabaseFactory("/databases/mydb");
close()
Close a database factory. This method frees some resources, but it’s not necessary to call it to unlock the databases.
Syntax:
void close()
Database create()
Creates a new database. If the database already exists, an exception is thrown.
Example:
DatabaseFactory arcade = new DatabaseFactory("/databases/mydb");
Database db = arcade.create();
Database open()
Opens an existent database in READ_WRITE mode. If the database does not exist, an exception is thrown.
Example:
DatabaseFactory arcade = new DatabaseFactory("/databases/mydb");
try( Database db = arcade.open(); ) {
// YOUR CODE
}
Database open(MODE mode)
Opens an existent database by specifying a mode between READ_WRITE and READ_ONLY mode. If the database does not exist, an exception is thrown. In READ_ONLY mode, any attempt to modify the database throws an exception.
Example:
DatabaseFactory arcade = new DatabaseFactory("/databases/mydb");
Database db = arcade.open(MODE.READ_ONLY);
try {
// YOUR CODE
} finally {
db.close();
}
Database Interface
It’s the main class to operate with ArcadeDB.
To obtain an instance of Database, use the class DatabaseFactory.
Methods
| Transaction | Lifecycle | Query | Records | Misc |
|---|---|---|---|---|
async()
It returns an instance of DatabaseAsyncExecutor to execute asynchronous calls.
Syntax:
DatabaseAsyncExecutor async()
Example:
Execute an asynchronous query:
db.async().query("sql", "select from User", new AsyncResultsetCallback() {
@Override
public void onComplete(ResultSet resultSet) {
while (resultSet.hasNext())
System.out.println( "Name = " + resultSet.next().getProperty("name"));
}
@Override
public void onError(Exception exception) {
System.err.println("Error on executing the query: " + exception );
}
});
begin()
Starts a transaction on the current thread.
Each thread can have only one active transaction.
All the modification to the database become persistent only at pending changes in the transaction are made persistent only when the commit() method is called.
ArcadeDB supports ACID transactions.
Before the commit, no other thread/client can see any of the changes contained in the current transaction.
Syntax:
begin()
Example:
db.begin(); // <--- AT THIS POINT THE TRANSACTION IS STARTED AND ALL THE CHANGES ARE COLLECTED TILL THE COMMIT (SEE BELOW)
try{
// YOUR CODE HERE
db.commit();
} catch( Exception e ){
db.rollback();
}
close()
Closes a database.
This method should be called at the end of the application.
By using Java7+ AutoClosed statement, the close() method is executed automatically at the end of the scope of the database variable.
Syntax:
void close()
Example:
Database db = new DatabaseFactory("/temp/mydb").open();
try{
// YOUR CODE HERE
} finally {
db.close();
}
The suggested method is using Java7+ AutoClosed statement, to avoid the explicit close() calling:
try( Database db = new DatabaseFactory("/temp/mydb").open(); ) {
// YOUR CODE
}
drop()
Drops a database. The database will be completely removed from the filesystem.
Syntax:
void drop()
Example:
new DatabaseFactory("/temp/mydb").open().drop();
getSchema()
Returns the Schema instance for the database.
Syntax:
Schema getSchema()
Example:
db.getSchema().createVertexType("Song");
getSize()
Returns the size of the database in bytes.
Syntax:
Schema getSize()
Example:
db.getSize();
isOpen()
Returns true if the database is open, otherwise false.
Syntax:
boolean isOpen()
Example:
if( db.isOpen() ){
// YOUR CODE HERE
}
query( language, command, positionalParameters )
Executes a query, with optional positional parameters.
This method only executes idempotent statements, namely SELECT and MATCH, that cannot change the database.
The execution of any other commands will throw a QueryNotIdempotentException exception.
A write nested anywhere in the statement makes it a write, as described for
query( language, command, positionalParameters ).
A statement is classified by everything it runs, not only by its first keyword. A write nested in a LET, a
projection, a FROM subquery, a CASE, a RETURN or a script block makes the whole statement a write, so
SELECT FROM (INSERT INTO T SET a = 1) or SELECT *, (DELETE FROM T) AS d FROM V is refused here: run it with
command(). PROFILE takes the classification of the statement it wraps, while EXPLAIN does not execute its
statement and stays a read. (Since v26.10.2; in v26.10.1 and earlier only the top-level statement was checked, so such
a nested write ran through query().)
Syntax:
Resultset query( String language, String command, Object... positionalParameters )
Where:
-
languageis the language to use. Only "SQL" language is supported for now, but in the future multiple languages could be used -
commandis the command to execute. If the language supports prepared statements (SQL does), you can specify parameters by using?for positional replacement -
positionalParametersoptional variable array of parameters to execute with the query
It returns a Resultset object where the result can be iterated.
With SQL, the number of positional values must match the placeholders of the statement: each ?, and each :name
bound positionally, takes exactly one value (pass null explicitly to bind a null). In a sqlscript the placeholders
of all its statements are counted together. A Postgres-style $N reads the value at position N and may leave lower
positions unread. A call that does not match is refused before the statement runs with a CommandSQLParsingException
whose message names both counts. A list meant for one placeholder, as in WHERE @rid IN ?, is passed as one value: a
List or a Java array (RID[], int[], …), which IN ? reads as the list of its elements. A parameter map (named
parameters) is not counted. The same rule applies to command() and to the remote Java client.
db.query("sql", "select from V where @rid in ?", List.of(rid1, rid2)); // one placeholder, one value
db.query("sql", "select from V where @rid in ?", rid1, rid2); // refused: 1 placeholder, 2 values
(since v26.11.1) Before, a surplus value was silently dropped (so where @rid in ? with two RIDs spread as
two values answered one row) and a missing value was bound to null.
|
Examples:
Simple query:
ResultSet resultset = db.query("sql", "select from V");
while (resultset.hasNext()) {
Result record = resultset.next();
System.out.println( "Found record, name = " + record.getProperty("name"));
}
Query passing positional parameters:
ResultSet resultset = db.query("sql", "select from V where age > ? and city = ?", 18, "Melbourne");
while (resultset.hasNext()) {
Result record = resultset.next();
System.out.println( "Found record, name = " + record.getProperty("name"));
}
To tell a property that is missing from one stored as null, use Result.getPropertyIfPresent(name, absentValue) (or Document.getIfPresent(name, absentValue) on a record). It returns the value, null included, when the property exists, and absentValue otherwise. It does in one lookup what hasProperty() followed by getProperty() does in two (since v26.10.1).
|
query( language, command, parameterMap )
Executes a query taking a map for parameters.
This method only executes idempotent statements, namely SELECT and MATCH, that cannot change the database.
The execution of any other commands will throw a QueryNotIdempotentException exception.
Syntax:
Resultset query( String language, String command, Map<String,Object> parameterMap )
Where:
-
languageis the language to use. Only "SQL" language is supported for now, but in the future multiple languages could be used -
commandis the command to execute. If the language supports prepared statements (SQL does), you can specify parameters by name by using:<arg-name> -
parameterMapthis map is used to extract the named parameters
It returns a Resultset object where the result can be iterated.
Examples:
Map<String,Object> parameters = new HashMap<>();
parameters.put("age", 18);
parameters.put("city", "Melbourne");
ResultSet resultset = db.query("sql", "select from V where age > :age and city = :city", parameters);
while (resultset.hasNext()) {
Result record = resultset.next();
System.out.println( "Found record, name = " + record.getProperty("name"));
}
command( language, command, positionalParameters )
Executes a command that could change the database.
This is the equivalent to query(), but allows the command to modify the database.
Only "SQL" language is supported, but in the future multiple languages could be used.
Syntax:
Resultset command( String language, String command, Object... positionalParameters )
Where:
-
languageis the language to use. Only "SQL" is supported -
commandis the command to execute. If the language supports prepared statements (SQL does), you can specify parameters by using?for positional replacement or by name by using:<arg-name> -
positionalParametersoptional variable array of parameters to execute with the query. With SQL their number must match the placeholders of the statement, see positional parameter count (since v26.11.1)
It returns a Resultset object where the result can be iterated.
Examples:
Create a new record:
db.command("sql", "insert into V set name = 'Jay', surname = 'Miner'");
Create a new record by passing position parameters:
db.command("sql", "insert into V set name = ?, surname = ?", "Jay", "Miner");
command( language, command, parameterMap )
Executes a command that could change the database.
This is the equivalent to query(), but allows the command to modify the database.
Only "SQL" language is supported, but in the future multiple languages could be used.
Syntax:
Resultset command( String language, String command, Map<String,Object> parameterMap )
Where:
-
languageis the language to use. Only "SQL" is supported -
commandis the command to execute. If the language supports prepared statements (SQL does), you can specify parameters by using?for positional replacement or by name by using:<arg-name> -
parameterMapthis map is used to extract the named parameters
It returns a Resultset object where the result can be iterated.
Examples:
Create a new record by passing a map of parameters:
Map<String,Object> parameters = new HashMap<>();
parameters.put("name", "Jay");
parameters.put("surname", "Miner");
db.command("sql", "insert into V set name = :name, surname = :surname", parameters);
commit()
Commits the thread’s active transaction.
All the pending changes in the transaction are made persistent.
A transaction must be begun by calling the begin() method.
Rolled back transactions cannot be committed.
ArcadeDB supports ACID transactions.
Before the commit, no other thread/client can see any of the changes contained in the current transaction.
ArcadeDB uses a WAL (Write Ahead Log) as journal in case a crash happens at commit time.
In this way, at the next restart, the database can be rollbacked at the previous state.
If the commit operation succeed, the changes are immediately visible to the other threads/clients and further transactions of the current thread.
Syntax:
commit()
Example:
db.begin();
try{
// YOUR CODE HERE
db.commit(); // <--- COMMIT ALL THE CHANGES "ALL OR NOTHING" IN PERSISTENT WAY
} catch( Exception e ){
db.rollback();
}
deleteRecord( record )
Deleted a record. The record will be persistently deleted only at commit time.
(Since v26.11.1) Creating an edge from or to a vertex deleted earlier in the same transaction fails with
VertexNotFoundException, through the Java API, SQL, Cypher and Gremlin alike, and nothing is stored. Before, the edge record
was committed without the deleted vertex (issue #10196).
Syntax:
void deleteRecord( Record record )
Examples:
db.deleteRecord( customer );
iterateBucket( bucketName )
Iterates all the records contained in a bucket.
To scan a type (with all its buckets), use the method iterateType() instead.
The result are not accumulated in RAM, but rather this method returns an Iterator<Record> that fetches the records only when .next() is called.
Syntax:
Iterator<Record> iterateBucket( String bucketName )
Example:
Aggregate the records by age. This is equivalent to a SQL query with a "group by age":
Map<String, AtomicInteger> aggregate = new HashMap<>();
Iterator<Record> result = db.iterateType("V", true );
while( result.hasNext() ){
Record record = result.next();
String age = (String) record.get("age");
AtomicInteger counter = aggregate.get(age);
if (counter == null) {
counter = new AtomicInteger(1);
aggregate.put(age, counter);
} else
counter.incrementAndGet();
}
Example:
Prints all the records in the bucket "Customer" with age major or equals to 21.
Iterator<Record> result = db.iterateBucket("Customer");
while( result.hasNext() ){
Record record = result.next();
final Document customer = record.asDocument();
final Integer age = customer.getInteger("age");
if (age != null && age >= 21 )
System.out.println("Found customer: " + customer.getString("name") );
}
iterateType( className, polymorphic )
Iterates all the records contained in the buckets relative to a type.
If polymorphic is true, then also the sub-types buckets are considered.
To iterate one bucket only check out the iterateBucket() method.
The result are not accumulated in RAM, but rather this method returns an Iterator<Record> that fetches the records only when .next() is called.
Syntax:
Iterator<Record> iterateType( String typeName, boolean polymorphic )
Example:
Aggregate the records by age. This is equivalent to a SQL query with a "group by age":
Map<String, AtomicInteger> aggregate = new HashMap<>();
Iterator<Record> result = db.iterateType("V", true );
while( result.hasNext() ){
Record record = result.next();
String age = (String) record.get("age");
AtomicInteger counter = aggregate.get(age);
if (counter == null) {
counter = new AtomicInteger(1);
aggregate.put(age, counter);
} else
counter.incrementAndGet();
}
lookupByKey( type, properties, keys )
Look ups for one or more records (document, vertex or edge) that match one or more indexed keys.
Syntax:
Cursor<RID> lookupByKey( String type, String[] properties, Object[] keys )
Where:
-
typetype name -
propertiesarray of property names to match -
keysarray of keys
It returns a Cursor<RID> (like an iterator).
The properties must be covered by a key index (LSM_TREE or HASH). A full-text index matches words rather than whole
values, so it cannot answer an exact lookup: lookupByKey() and newEdgeByKeys() throw an IllegalArgumentException
when the only index on the properties is FULL_TEXT, as they do when there is no index at all.
(Since v26.10.1: before, a full-text index returned every record sharing a word with the key.)
Examples:
Look up for an author with name "Jay" and surname "Miner". This requires an index on the type "Author", properties "name" and "surname".
Cursor<RID> jayMiner = database.lookupByKey("Author", new String[] { "name", "surname" }, new Object[] { "Jay", "Miner" });
while( jayMiner.hasNext() ){
System.out.println( "Found Jay! " + jayMiner.next().getProperty("name"));
}
lookupByRID( rid, loadContent )
Look ups for a record (document, vertex or edge) by its RID (Record Identifier).
Syntax:
Record lookupByRID( RID rid, boolean loadContent )
Where:
-
ridis the record identifier -
loadContentforces the load of the content too. If the content is not loaded will be lazy loaded at the first access. Usetrueif you are going to access to the record content for sure, otherwise, usefalse
It returns a Record implementation (document, vertex or edge).
Examples:
Load the vertex by RID and its content:
Vertex v = (Vertex) db.lookupByRID(new RID("#3:47"));
newDocument( typeName )
Creates a new document of a certain type.
The type must be of type "document" and must be created beforehand.
In order to be saved, the method MutableDocument.save() must be called.
Syntax:
MutableDocument newDocument( typeName )
Where:
-
typeNametype name
It returns a MutableDocument instance.
Examples:
Create a new document of type "Customer":
MutableDocument doc = db.newDocument("Customer");
doc.set("name", "Jay");
doc.set("surname", "Miner");
doc.save(); // THE DOCUMENT IS SAVED IN THE DATABASE ONLY WHEN `.save()` IS CALLED
The metadata names @rid, @type, @cat, @in, @out, @props and @this are not properties. Since
v26.10.2 the positional setters (set(name, value), set(n1, v1, n2, v2), set(Object…)) refuse them with an
IllegalArgumentException, before assigning anything; earlier versions stored them as properties no reader could
return. Another name starting with @ (for example @special) is an ordinary property. The map-based setters
(set(Map), fromMap(), fromJSON(), merge()) skip every key starting with @ as metadata, so the output of
toMap(true) can be fed back. On an edge, @in and @out re-point the edge through every one of these setters
(earlier versions did it only through set(name, value) and merge()).
|
newVertex( typeName )
Creates a new vertex of a certain type.
The type must be of type "vertex" and must be created beforehand.
In order to be saved, the method MutableVertex.save() must be called.
Syntax:
MutableVertex newVertex( typeName )
Where:
-
typeNametype name
It returns a MutableVertex instance.
Examples:
Create a new document of type "Customer":
MutableVertex v = db.newVertex("Customer");
v.set("name", "Jay");
v.set("surname", "Miner");
v.save();
Removing edges while iterating a vertex
The iterators returned by Vertex.getEdges() and Vertex.getVertices(), with or without edge types, support
Iterator.remove(). It deletes, through the database, the edge of the entry the last next() returned (for
getVertices(), the edge leading to the vertex just returned; the vertex itself stays): the DELETE_RECORD permission is
checked, the indexes on the edge are updated, the record events fire and the edge is unlinked from the vertex at the
other end too, exactly as edge.delete() does. If the delete is refused (a security error, a before-delete listener
vetoing it), the exception reaches the caller and the entry stays in place. A second remove() without a next() in
between throws IllegalStateException.
Iterator<Edge> it = v.getEdges(Vertex.DIRECTION.OUT, "IsFriendOf").iterator();
while (it.hasNext())
if (it.next().getInteger("since") < 2000)
it.remove();
(Since v26.10.2) In 26.10.1 and earlier, an iterator over given edge types only unlinked the entry from the walked vertex, leaving the edge record and the other vertex’s link behind, and an iterator over every edge type logged a refused delete and unlinked the entry anyway.
newEdgeByKeys( sourceVertexType, sourceVertexKey, sourceVertexValue, destinationVertexType, destinationVertexKey, destinationVertexValue, createVertexIfNotExist, edgeType, bidirectional, properties )
Creates a new edge between two vertices found by their keys.
Syntax:
Edge newEdgeByKeys( String sourceVertexType, String[] sourceVertexKey,
Object[] sourceVertexValue,
String destinationVertexType, String[] destinationVertexKey,
Object[] destinationVertexValue,
boolean createVertexIfNotExist, String edgeType, boolean bidirectional,
Object... properties )
Where:
-
sourceVertexTypesource vertex type name -
sourceVertexKeysource vertex key properties -
sourceVertexValuesource vertex key values -
destinationVertexTypedestination vertex type name -
destinationVertexKeydestination vertex key properties -
destinationVertexValuedestination vertex key values -
createVertexIfNotExistcreates source and/or destination vertices if not exist -
edgeTypeedge type name -
bidirectionaltrueif the edge must be bidirectional, otherwisefalse -
propertiesoptional property array with pairs of name (as string) and value
It returns a MutableEdge instance.
Examples:
Create a new document of type "Customer":
Edge likes = db.newEdgeByKeys( "Account",
new String[] {"id"}, new Object[] {322323},
"Song",
new String[] {"title"},
new Object[] {"Chasing Cars"},
false, "Likes", true);
likes.save();
rollback()
Aborts the thread’s active transaction by rolling back all the pending changes.
Usually the transaction rollback is executed in case of errors.
If an exception happens during the call commit(), the transaction is roll backed automatically.
Once rolled backed, the transaction cannot be committed anymore but it has to be re-started by calling the begin() method.
Syntax:
rollback()
Example:
db.begin();
try{
// YOUR CODE HERE
db.commit();
} catch( Exception e ){
db.rollback(); // <--- ROLLBACK IN CASE OF EXCEPTION
}
scanBucket( bucketName, callback )
Scans all the records contained in a buckets.
For each record found, the callback is called passing the current record.
To scan a type (with all its buckets), use the method scanType() instead.
The callback method must return true to continue the scan, otherwise false.
Look also at the iterateBucket() method if you want to use an iterator approach instead of callback.
Syntax:
void scanBucket(String bucketName, RecordCallback callback);
Example:
Prints all the records in the bucket "Customer" with age major or equals to 21.
db.scanBucket("Customer", (record) -> {
final Document customer = record.asDocument();
final Integer age = customer.getInteger("age");
if (age != null && age >= 21 )
System.out.println("Found customer: " + customer.getString("name") );
return true;
});
scanType( className, polymorphic, callback )
Scans all the records contained in all the buckets relative to a type.
If polymorphic is true, then also the sub-types buckets are considered.
For each record found, the callback is called passing the current record.
To scan one bucket only check out the scanBucket() method.
The callback method must return true to continue the scan, otherwise false.
Look also at the iterateType() method if you want to use an iterator approach instead of callback.
Syntax:
scanType( String className, boolean polymorphic, DocumentCallback callback )
Example:
Aggregate the records by age. This is equivalent to a SQL query with a "group by age":
Map<String, AtomicInteger> aggregate = new HashMap<>();
db.scanType("V", true, (record) -> {
String age = (String) record.get("age");
AtomicInteger counter = aggregate.get(age);
if (counter == null) {
counter = new AtomicInteger(1);
aggregate.put(age, counter);
} else
counter.incrementAndGet();
return true;
});
select()
Returns a Select builder to construct and execute queries using the Native Select API.
This provides a zero-parsing, JIT-friendly fluent query builder as an alternative to SQL or Cypher.
Syntax:
Select select()
It returns a Select instance that supports fluent chaining for building queries.
Examples:
Simple query:
Vertex v = db.select().fromType("Person")
.where().property("name").eq().value("John")
.vertices().nextOrNull();
With count:
long count = db.select().fromType("Person")
.where().property("active").eq().value(true)
.count();
With stream:
List<String> names = db.select().fromType("Person")
.where().property("age").gt().value(18)
.stream()
.map(d -> d.getString("name"))
.collect(Collectors.toList());
See the Native Select API documentation for the full reference including operators, graph traversal, and vector search.
transaction( txBlock )
This methods wraps a call to the method transaction with retries by using the default retries specified in the database setting arcadedb.mvccRetries.
transaction( txBlock, retries )
Executes a transaction block as a callback or a clojure.
Before calling the callback in TransactionScope, the transaction is begun and after the end of the callback, the transaction is committed.
In case of any exceptions, the transaction is rolled back.
In case a NeedRetryException exceptions is thrown (a ConcurrentModificationException, a LockTimeoutException from
the commit’s file locks, and the rest of that family), the transaction is repeated up to retries times. A
DuplicatedKeyException is retried once: a genuine duplicate fails the same way on every attempt, and only a concurrent
insert that committed between two attempts, as in an upsert that lost the race, can turn it into a success. The same
rules apply to async().transaction() and to SQL COMMIT RETRY (since v26.11.1).
(Since v26.10.2) A NeedRetryException whose isRetryableOnThisNode() returns false is rethrown on the first
attempt. That is a refusal that would repeat on every attempt against this node: a ServerIsNotTheLeaderException
raised by an HA follower that refuses leader-only work (a schema change, a security mutation) and names the leader to
send it to. A refusal raised during an election, which names no leader, is still retried. In 26.10.1 and earlier such a
refusal was retried retries times, with the retry delay between attempts, before reaching the caller.
|
The retry is skipped if the block already committed part of its work before failing. This happens
when the block runs a statement with an explicit batch boundary, such as UPDATE … BATCH n,
DELETE … BATCH n or MOVE VERTEX … BATCH n: those commit every n records by design, so the
rollback can no longer undo the records already committed and repeating the block would apply them a
second time. In that case the exception is propagated to the caller, who is the only one who knows how
to compensate for the part that already went through. TRUNCATE TYPE and REBUILD TYPE are not
affected, because they skip their internal batching when they run inside a transaction opened by the
caller.
|
Syntax:
void transaction( TransactionScope txBlock )
Examples:
Example by using Java21+ syntax:
db.transaction( () -> {
final MutableVertex v = database.newVertex("Author");
v.set("name", "Jay");
v.set("surname", "Miner");
v.save();
});
Example by using Java7 syntax:
db.transaction( new Database.TransactionScope() {
@Override
public void execute() {
final MutableVertex v = db.newVertex("Author");
v.set("name", "Jay");
v.set("surname", "Miner");
v.save();
}
});