Java driver tutorial

In this tutorial, we’ll use a sample application to showcase how to define a schema, insert initial data, read data, apply pagination and inference, and add additional data.

Prerequisites

Deploy TypeDB Cloud, or install and run TypeDB Core.

Install the Java driver.

Running the sample app will result in a deletion of a database named iam if it exists already on the server.

Source code

See the full source code of the sample app below:

package org.example2;

import com.vaticle.typedb.driver.api.TypeDBDriver;
import com.vaticle.typedb.driver.api.TypeDBOptions;
import com.vaticle.typedb.driver.api.TypeDBSession;
import com.vaticle.typedb.driver.api.TypeDBTransaction;
import com.vaticle.typedb.driver.TypeDB;
import com.vaticle.typeql.lang.TypeQL;
import static com.vaticle.typeql.lang.TypeQL.*;
import com.vaticle.typeql.lang.query.TypeQLGet;
import com.vaticle.typeql.lang.query.TypeQLInsert;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;

public class Main {
    static int k = 0; // Counter
    public static void main(String[] args) {
        final String DB_NAME = "iam"; // Name of a database to connect to
        final String SERVER_ADDR = "127.0.0.1:1729"; // Address of a TypeDB Core server to connect to

        System.out.println("IAM Sample App");

        System.out.println("Attempting to connect to a TypeDB Core server: " + SERVER_ADDR);
        TypeDBDriver driver = TypeDB.coreDriver(SERVER_ADDR); // the driver is connected to the server
        if (driver.databases().contains(DB_NAME)) {
            System.out.println("Found a pre-existing database! Re-creating with the default schema and data...");
            driver.databases().get(DB_NAME).delete();
        }
        driver.databases().create(DB_NAME);
        if (driver.databases().contains(DB_NAME)) {
            System.out.println("Empty database created");
        }
        System.out.println("Opening a Schema session to define a schema.");
        try (TypeDBSession session = driver.session(DB_NAME, TypeDBSession.Type.SCHEMA)) {
            try (TypeDBTransaction writeTransaction = session.transaction(TypeDBTransaction.Type.WRITE)) {
                String defineQuery = Files.readString(Paths.get("iam-schema.tql"));
                writeTransaction.query().define(defineQuery);
                writeTransaction.commit();

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("Opening a Data session to insert data.");
        try (TypeDBSession session = driver.session(DB_NAME, TypeDBSession.Type.DATA)) {
            try (TypeDBTransaction writeTransaction = session.transaction(TypeDBTransaction.Type.WRITE)) {
                String insertQuery = Files.readString(Paths.get("iam-data-single-query.tql"));
                writeTransaction.query().insert(insertQuery);
                writeTransaction.commit();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

            System.out.println("Testing the new database.");
            try (TypeDBTransaction readTransaction = session.transaction(TypeDBTransaction.Type.READ)) { // Re-using a session to open a new transaction
                long result = readTransaction.query().getAggregate("match $u isa user; get $u; count;").resolve().get().asLong();
                if (result == 3) {
                    System.out.println("Database setup complete. Test passed.");
                } else {
                    System.out.println("Test failed with the following result:" + result + " expected result: 3.");
                    System.exit(0);
                }
            }
        }

        System.out.println("Commencing sample requests");
        try (TypeDBSession session = driver.session(DB_NAME, TypeDBSession.Type.DATA)) {

            System.out.println("");
            System.out.println("Request #1: User listing");
            try (TypeDBTransaction readTransaction = session.transaction(TypeDBTransaction.Type.READ)) {
                k = 0; // reset the counter
                readTransaction.query().get( // Executing query
                        "match $u isa user, has full-name $n, has email $e; get;" // TypeQL query string
                ).forEach(result -> { // Iterating through results
                    String name = result.get("n").asAttribute().getValue().asString();
                    String email = result.get("e").asAttribute().getValue().asString();
                    k += 1;
                    System.out.println("User #" + k + ": " + name + ", has E-mail: " + email);
                });
                System.out.println("Users found: " + k);
            }

            System.out.println("");
            System.out.println("Request #2: Files that Kevin Morrison has access to");
            try (TypeDBTransaction readTransaction = session.transaction(TypeDBTransaction.Type.READ)) {
                TypeQLGet getQuery = TypeQL.match( // Java query builder to prepare TypeQL query string
                        cVar("u").isa("user").has("full-name", "Kevin Morrison"),
                        cVar("p").rel(cVar("u")).rel(cVar("pa")).isa("permission"),
                        cVar("o").isa("object").has("path", cVar("fp")),
                        cVar("pa").rel(cVar("o")).rel(cVar("va")).isa("access")
                ).get(cVar("fp"));
                k = 0;
                readTransaction.query().get(getQuery).forEach(result -> { // Executing query
                    k += 1;
                    System.out.println("File #" + k + ": " + result.get("fp").asAttribute().getValue().asString());
                });
                System.out.println("Files found: " + k);
            }

            System.out.println("");
            System.out.println("Request #3: Files that Kevin Morrison has view access to (with inference)");
            TypeDBOptions options = new TypeDBOptions().infer(true);
            try (TypeDBTransaction readTransaction = session.transaction(TypeDBTransaction.Type.READ, options)) {
                TypeQLGet getQuery = TypeQL.match( // Java query builder to prepare TypeQL query string
                        cVar("u").isa("user").has("full-name", "Kevin Morrison"),
                        cVar("p").rel(cVar("u")).rel(cVar("pa")).isa("permission"),
                        cVar("o").isa("object").has("path", cVar("fp")),
                        cVar("pa").rel(cVar("o")).rel(cVar("va")).isa("access"),
                        cVar("va").isa("action").has("name", "view_file")
                ).get(cVar("fp")).sort(cVar("fp")).offset(0).limit(5);
                k = 0;
                readTransaction.query().get(getQuery).forEach(result -> { // Executing query
                    k += 1;
                    System.out.println("File #" + k + ": " + result.get("fp").asAttribute().getValue().asString());
                });

                getQuery = TypeQL.match( // Java query builder to prepare TypeQL query string
                        cVar("u").isa("user").has("full-name", "Kevin Morrison"),
                        cVar("p").rel(cVar("u")).rel(cVar("pa")).isa("permission"),
                        cVar("o").isa("object").has("path", cVar("fp")),
                        cVar("pa").rel(cVar("o")).rel(cVar("va")).isa("access"),
                        cVar("va").isa("action").has("name", "view_file")
                ).get(cVar("fp")).sort(cVar("fp")).offset(5).limit(5);
                readTransaction.query().get(getQuery).forEach(result -> { // Executing query
                    k += 1;
                    System.out.println("File #" + k + ": " + result.get("fp").asAttribute().getValue().asString());
                });
                System.out.println("Files found: " + k);
            }

            System.out.println("");
            System.out.println("Request #4: Add a new file and a view access to it");
            try (TypeDBTransaction writeTransaction = session.transaction(TypeDBTransaction.Type.WRITE)) { // WRITE transaction is open
                String filepath = "logs/" + new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS").format(new Date(System.currentTimeMillis())) + ".log";
                TypeQLInsert insertQuery = TypeQL.insert(cVar("f").isa("file").has("path", filepath));
                System.out.println("Inserting file: " + filepath);
                writeTransaction.query().insert(insertQuery); // Executing the query
                insertQuery = TypeQL.match(
                        cVar("f").isa("file").has("path", filepath),
                        cVar("vav").isa("action").has("name", "view_file")
                ).insert(
                    cVar("pa").rel(cVar("vav")).rel(cVar("f")).isa("access")
                );
                System.out.println("Adding view access to the file");
                writeTransaction.query().insert(insertQuery); // Executing the second query
                writeTransaction.commit();
            }
        }
        driver.close(); // closing server connection
    }
}

Additional files

Schema and initial data

For running the sample application, use the following schema and insert query for the sample dataset insertion.

Building and running

Build and run a maven project from within your IDE.
Alternatively, to build a .jar file, run:

mvn clean install

The TypeQL files with Define and Insert queries should be placed at the top level of the project directory, side by side with the pom.xml file.

Explanation

This section is for comments and explanation of the sample application implementation details.

Database setup

First of all, we connect to a TypeDB Core server by initializing a driver instance.

We proceed to check whether a database named iam exists on the server already. We delete such a database if it exists to prevent any possible schema or data conflicts. Then we create a new database with the name iam.

We open a schema session to this database, open a write transaction, read the iam-schema.tql file, and send its contents as a Define query. Next, we commit the transaction to persist the resulted schema.

After that we open a data session, a write transaction, read the iam-data-single-query.tql file, and send its contents as an Insert query. We commit the transaction again and re-using the same data session to open a read transaction.

With the new read transaction, we test the resulted database by retrieving the number of users in it.

For more information on server connections, sessions, and transactions, see the Connecting to TypeDB page.

Request #1: User listing

We open a data session that will be re-used for multiple requests, open a read transaction, and send a simple TypeQL query to get all users with full-name and email attributes.

match
$u isa user,
    has full-name $n,
    has email $e;
get;

We iterate through results to print every user and the total count of users.

Query explanation

This is a Get query that includes match and get clauses.

We go through all entities of the user type that have a full-name attribute and email attribute.

A get clause with no variables and just a semicolon means, that we are retrieving concepts for all variables from the preceding match clause pattern in every result.

Request #2: Files

We open a new read transaction in the same data session and send a TypeQL query to get paths of all filenames that user with the full-name of Kevin Morrison has access to.

match
$u isa user, has full-name 'Kevin Morrison';
$p($u, $pa) isa permission;
$o isa object, has path $fp;
$pa($o, $va) isa access;
get $fp;

For Java implementation, we use Java query builder syntax to programmatically build a TypeQL query string.

Java query builder syntax
TypeQLGet getQuery = TypeQL.match( // Java query builder to prepare TypeQL query string
                                    cVar("u").isa("user").has("full-name", "Kevin Morrison"),
                                    cVar("p").rel(cVar("u")).rel(cVar("pa")).isa("permission"),
                                    cVar("o").isa("object").has("path", cVar("fp")),
                                    cVar("pa").rel(cVar("o")).rel(cVar("va")).isa("access")
                            ).get(cVar("fp"));

We iterate through results to print every file and the total count of files.

Query explanation

This is a Get query that includes match and get clauses.

A match clause pattern looks for a user (variable $u) with attribute full-name of value Kevin Morrison assigned, a permission relation ($p) in between this user $u and potential access $pa. Additionally, we state that an object ($o) with a path $fp should be a part of $pa access relation, without having to specify what kind of action $va it should be.

A get clause filters response to only contain the matched path attributes ($fp).

Request #3: Inference

We start with opening a new transaction with the inference option enabled.

Then we send a query to get Kevin’s files, but this time we specify the access action to be view_file, since the permission for this access wasn’t inserted in the sample dataset and must be inferred by the add-view-permission rule. We also add sort, offset and limit modifiers to the query to manage pagination. This request consists of two queries splitting the results between queries.

Query 1
match
$u isa user, has full-name 'Kevin Morrison';
$p($u, $pa) isa permission;
$o isa object, has path $fp;
$pa($o, $va) isa access;
$va isa action, has name 'view_file';
get $fp;
sort $fp asc;
offset 0;
limit 5;

And then, we repeat the query with different offset to get the rest of results.

Query 2
match
$u isa user, has full-name 'Kevin Morrison';
$p($u, $pa) isa permission;
$o isa object, has path $fp;
$pa($o, $va) isa access;
$va isa action, has name 'view_file';
get $fp;
sort $fp asc;
offset 5;
limit 5;

For Java implementation, we use Java query builder syntax to programmatically build a TypeQL query string.

Java query builder syntax for the Query 1
TypeQLGet getQuery = TypeQL.match( // Java query builder to prepare TypeQL query string
                                    cVar("u").isa("user").has("full-name", "Kevin Morrison"),
                                    cVar("p").rel(cVar("u")).rel(cVar("pa")).isa("permission"),
                                    cVar("o").isa("object").has("path", cVar("fp")),
                                    cVar("pa").rel(cVar("o")).rel(cVar("va")).isa("access"),
                                    cVar("va").isa("action").has("name", "view_file")
                            ).get(cVar("fp")).sort(cVar("fp")).offset(0).limit(5);
Java query builder syntax for the Query 2
getQuery = TypeQL.match( // Java query builder to prepare TypeQL query string
                        cVar("u").isa("user").has("full-name", "Kevin Morrison"),
                        cVar("p").rel(cVar("u")).rel(cVar("pa")).isa("permission"),
                        cVar("o").isa("object").has("path", cVar("fp")),
                        cVar("pa").rel(cVar("o")).rel(cVar("va")).isa("access"),
                        cVar("va").isa("action").has("name", "view_file")
                 ).get(cVar("fp")).sort(cVar("fp")).offset(5).limit(5);

Query explanation

We update the previous query to set the type of action ($va) to the view_file value. We still get only path ($fp), but now we add sorting and pagination: we set offset and limit for the results.

Note that Kevin has been assigned only modify_file access, and the view_file access is being inferred by a rule. To use inference in this query, we modify TypeDB options and send the modified set of options to the transaction call.

Request #4: Add a new file

To be able to insert new data, we open a write transaction this time. Then we generate a filename from current time and date and insert an instance of the file type that owns the attribute of type path and value of the generated filename.

Query 1
insert $f isa file, has path 'logs/2023-12-19-19-11-36.log';

After that we do another query to match the new file we just inserted and add a relation of the access type with it:

Query 2
match
$f isa file, has path 'logs/2023-12-19-19-11-36.log';
$vav isa action, has name 'view_file';
insert
($vav, $f) isa access;

For Java implementation, we use Java query builder syntax to programmatically build a TypeQL query string.

Java query builder syntax for the Query 1
TypeQLInsert insertQuery = TypeQL.insert(cVar("f").isa("file").has("path", filepath));
Java query builder syntax for the Query 2
insertQuery = TypeQL.match(
                            cVar("f").isa("file").has("path", filepath),
                            cVar("vav").isa("action").has("name", "view_file")
                    ).insert(
                            cVar("pa").rel(cVar("vav")).rel(cVar("f")).isa("access")
);

Query explanation

The first query inserts a file entity that owns a path attribute with the value of a generated filename.

With the second query, we look for a file entity that has the attribute path with the value we generated before. And we find an action, that has a name attribute with the value of view_file. Finally, we insert an access relation between the file and the action.

After both requests are done, we commit the write transaction to persist new information.

Note that we create the file first. If we try to insert an access to a nonexistent file our query will run but will not insert any new data (relation) because its match clause will find no matches in a database.

Provide Feedback