Skip to content

Latest commit

 

History

History
365 lines (280 loc) · 12.7 KB

java.md

File metadata and controls

365 lines (280 loc) · 12.7 KB

Java Documentation

The complete Java example is here

How to synchronously wait for a result

The driver's query method returns an instance of java.util.concurrent.CompletableFuture. In order to synchronously wait for the query result, use the CompletableFuture#get(long, TimeUnit) method.

future.get(5, TimeUnit.SECONDS);

Waiting for queries to complete is discourage in production code. Please prefer the asynchronous API provided by the CompletableFuture class, such as CompletableFuture#whenComplete or CompletableFuture#whenCompleteAsync.

Please note that this documentation uses an overload of the CompletableFuture#get method without a timeout. Although convenient for testing, this method can lead to poor performance in Java 8.

How to create an admin connection to Fauna.

An admin connection should only be used to create top level databases. After the database is created, a separate client connection should be created.

If you are using the FaunaDB-Cloud version:

  • remove the 'withEndpoint' line below
  • substitute "secret" for your authentication key's secret
FaunaClient adminClient = FaunaClient.builder()
    .withEndpoint("http://127.0.0.1:8443")
    .withSecret("secret")
    .build();

How to conditionally create a database

String DB_NAME = "demo";

Value dbResults = adminClient.query(
    Do(
        If(
            Exists(Database(DB_NAME)),
            Delete(Database(DB_NAME)),
            Value(true)
        ),
        CreateDatabase(Obj("name", Value(DB_NAME)))
    )
).get();
System.out.println("Successfully created database: " + dbResults.at("name").to(String.class).get() + "\n" + dbResults + "\n");

How to create a client connection to the database

After the database is created, a new key specific to that database can be used to create a client connection to that database.

Value keyResults = adminClient.query(
    CreateKey(Obj("database", Database(Value(DB_NAME)), "role", Value("server")))
).get();

String key = keyResults.at("secret").to(String.class).get();
FaunaClient client = adminClient.newSessionClient(key);
System.out.println("Connected to Fauna database " + DB_NAME + " with server role\n");

How to create a collection and index

String SPELLS_COLLECTION = "spells";
String INDEX_NAME = "spells_index";

Value collectionResults = client.query(
    CreateCollection(
        Obj("name", Value(SPELLS_COLLECTION))
    )
).get();
System.out.println("Create Collection for " + DB_NAME + ":\n " + collectionResults + "\n");

Value indexResults = client.query(
    CreateIndex(
        Obj("name", Value(INDEX_NAME), "source", Collection(Value(SPELLS_COLLECTION)))
    )
).get();
System.out.println("Create Index for " + DB_NAME + ":\n " + indexResults + "\n");

How to add entries to a collection

Value addFireResults = client.query(
    Create(
        Collection(Value(SPELLS_COLLECTION)),
        Obj("data",
            Obj("name", Value("Fire Beak"), "element", Value("water"), "cost", Value(15))
        )
    )
).get();
System.out.println("Added spell to collection " + SPELLS_COLLECTION + ":\n " + addFireResults + "\n");

Value addHippoResults = client.query(
    Create(
        Collection(Value(SPELLS_COLLECTION)),
        Obj("data",
            Obj("name", Value("Hippo's Wallow"), "element", Value("water"), "cost", Value(35))
        )
    )
).get();
System.out.println("Added spell to collection " + SPELLS_COLLECTION + ":\n " + addHippoResults + "\n");

How to access objects fields and convert to primitive values

Adding data to a collection returns a reference to the resource with the reference, a timestamp and the corresponding object in a json structure like:

{
  "object" : {
    "ref" : {
      ....
    },
    "ts" : 1528414251402950,
    "data" : {
      ....
    }
  }
}

Objects fields are accessed through at methods of Value class. It's possible to access fields by names if the value represents an object or by index if it represents an array. For example to retrieve the resource reference of the returned Value use the following to get the ref field:

//The results at 'ref' are a resource pointer to the collection that was just created.
Value hippoRef = addHippoResults.at("ref");
System.out.println("hippoRef = " + hippoRef);

How to execute a query

The query method takes an Expr object. Expr objects can be composed with others Expr to create complex query objects. com.faunadb.client.query.Language is a helper class where you can find all available expressions in the library.

Value readHippoResults = client.query(
    Select(Value("data"),Get(hippoRef))
).get();
System.out.println("Hippo Spells:\n " + readHippoResults + "\n");

The query method also accepts a timeout parameter. The timeout value defines the maximum time a query will be allowed to run on the server. If the value is exceeded, the query is aborted.

import java.time.Duration;

Duration timeout = Duration.ofMillis(500);

client.query(
    Select(Value("data"), Get(hippoRef)),
    timeout;
);

How to retrieve the values from a query result

That query returns the data in the form of a json object. It's possible to convert Value class to its primitive correspondent using to methods specifying a type. For example the data can be extracted from the results by using:

//convert the hippo results into primitive elements
String name = readHippoResults.at("name").to(String.class).get();
Integer cost = readHippoResults.at("cost").to(Integer.class).get();
String element = readHippoResults.at("element").to(String.class).get();

System.out.println(String.format(
    "Spell Details: Name=%s, Const=%d, Element=%s", name, cost, element));

Later on we will show a better method for converting to native types with User Defined types that do this transformation automatically.

How to safely work with result objects

This object represents the result of an operation and it might be success or a failure. All conversion operations returns an object like this. This way it's possible to avoid check for nullability everywhere in the code.

//This would return an empty option if the field is not found or the conversion fails
Optional<String> optSpellElement = readHippoResults.at("element").to(String.class).getOptional();
if (optSpellElement.isPresent()) {
    String element2 = optSpellElement.get();
    System.out.println("optional spell element = " + element2);
}
else {
    System.out.println("Something went wrong reading the spell");
}

Optionally it's possible transform one Result<T> into another Result<T> of different type using map and flatMap. If the result represents an failure all calls to map and flatMap are ignored and it returns a new failure with the same error message. See com.faunadb.client.types.Result for details.

How to execute a list query and retrieve a collection of the results

The query method takes an Expr object. Expr objects can be composed with others Expr to create complex query objects. com.faunadb.client.query.Language is a helper class where you can find all available expressions in the library.

Value queryIndexResults = client.query(
    SelectAll(Path("data", "id"),
        Paginate(
            Match(Index(Value(INDEX_NAME)))
        ))
).get();

That query returns a list of resource references to all the spells in the index. The list of references can be extracted from the results by using:

Collection<String>  spellsRefIds = queryIndexResults.asCollectionOf(String.class).get();
System.out.println("spellsRefIds = " + spellsRefIds);

How to work with user defined classes

Instead of manually creating your objects via the DSL (e.g. the Obj()), you can use annotations to automatically encode and decode the collection to user-defined types. These transform the types into the equivalent Value types.

For example a Spell collection could be used that defines the fields and constructor:

import com.faunadb.client.types.FaunaConstructor;
import com.faunadb.client.types.FaunaField;

public class Spell {

    @FaunaField private String name;
    @FaunaField private String element;
    @FaunaField private int cost;

    @FaunaConstructor
    public Spell(@FaunaField("name") String name, @FaunaField("element") String element, @FaunaField("cost") int cost) {
        this.name = name;
        this.element = element;
        this.cost = cost;
    }

    public String getName() {
        return name;
    }

    public String getElement() {
        return element;
    }

    public int getCost() {
        return cost;
    }

    @Override
    public String toString() {
        return "Spell{" +
            "name='" + name + '\'' +
            ", element='" + element + '\'' +
            ", cost=" + cost +
            '}';
    }
}

There are three attributes that can be used to change the behavior of the Encoder and Decoder:

  • FaunaField: Used to override a custom field name and/or provide a default value for that field. If this attribute is not specified, the member name will be used instead. Can be used on fields, properties and constructor arguments.
  • FaunaConstructor: Used to mark a constructor or a public static method as the method used to instantiate the specified type. This attribute can be used only once per class.
  • FaunaIgnore: Used to ignore a specific member. Can be used on fields, properties and constructors arguments. If used on a constructor argument, that argument must have a default value.

Encoding and decoding user defined classes

To persist an document of Spell in FaunaDB:

Spell newSpell = new Spell("Water Dragon's Claw", "water", 25);
Value storeSpellResult = client.query(
    Create(
        Collection(Value(SPELLS_COLLECTION)),
        Obj("data", Value(newSpell))
    )
).get();
System.out.println("Stored spell:\n " + storeSpellResult);

Read the spell we just created and convert from a Value type back to the Spell type:

Value dragonRef = storeSpellResult.at("ref");
Value getDragonResult = client.query(
    Select(Value("data"),
        Get(dragonRef)
    )
).get();
Spell spell = getDragonResult.to(Spell.class).get();
System.out.println("dragon spell: " + spell);

Encoding and decoding lists of user defined classes

To persist a Java list of Spell to FaunaDB encode the list into a Value:

Spell spellOne = new Spell("Chill Touch", "ice", 18);
Spell spellTwo = new Spell("Dancing Lights", "fire", 45);
Spell spellThree = new Spell("Fire Bolt", "fire", 32);
List<Spell> spellList = Arrays.asList(spellOne, spellTwo, spellThree);

//Lambda Variable for each spell
String NXT_SPELL = "NXT_SPELL";

//Encode the list of spells into an expression
Expr encodedSpellsList = Value(spellList);

//This query can be approximately read as for each spell in the list of spells evaluate the lambda function.
//That lambda function creates a temporary variable with each spell in the list and passes it to the create function.
//The create function then stores that spell in the database
Value spellsListSave = client.query(
    Foreach(
        encodedSpellsList,
        Lambda(Value(NXT_SPELL),
            Create(
                Collection(Value(SPELLS_COLLECTION)),
                Obj("data", Var(NXT_SPELL))
            )
        )
    )
).get();

System.out.println("Created list of spells from java list: \n" + spellsListSave);
Collection<Spell> spellCollection = spellsListSave.asCollectionOf(Spell.class).get();
System.out.println("save " + spellCollection.size() + " spells:");
spellCollection.forEach(nextSpell -> System.out.println("   " + nextSpell));

Read a list of all Spells out of the Spells index and decode back to a Java List of Spells:

//Lambda Variable for each spell ref
String REF_SPELL_ID = "NXT_SPELL";

//Map is equivalent to a functional map which maps over the set of all values returned by the paginate.
//Then for each value in the list it runs the lambda function which gets and returns the value.
//Map returns the data in a structure like this -> {"data": [ {"data": ...}, {"data": ...} ]} so the data field needs to be selected out.
//SelectAll does this by selecting the nested data with the Path("data", "data")
Value findAllSpells = client.query(
    SelectAll(Path("data", "data"),
        Map(
            Paginate(
                Match(Index(Value(INDEX_NAME)))
            ),
            Lambda(Value(REF_SPELL_ID), Get(Var(REF_SPELL_ID))))
    )
).get();

Collection<Spell> allSpellsCollection = findAllSpells.asCollectionOf(Spell.class).get();
System.out.println("read " + allSpellsCollection.size() + " spells:");
allSpellsCollection.forEach(nextSpell -> System.out.println("   " + nextSpell));