Skip to content

Latest commit

 

History

History
297 lines (225 loc) · 10.3 KB

scala.md

File metadata and controls

297 lines (225 loc) · 10.3 KB

Scala Documentation

The complete Scala example is here

Handling Fauna query results

A fauna query returns a future of a result. In real production code you should never block on a future and instead handle it async with map, flatMap, etc.

Only for demo purposes we are wrapping queries with await which blocks for the query to return and gets the result back. Never do this in production code.

This small helper method is used to make wrapping every call in await easier:

    def await[T](f: Future[T]): T = Await.result(f, 5.second)

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 'endpoint' argument below
  • substitute "secret" for your authentication key's secret
   val adminClient = FaunaClient("secret", "http://127.0.0.1:8443")

How to conditionally create a database

    val DB_NAME = "demo"

    val dbResults = await(adminClient.query(
      Do(
        If(
          Exists(Database(DB_NAME)),
          Delete(Database(DB_NAME)),
          true),
        CreateDatabase(Obj("name" -> DB_NAME)
        )
      )
    ))
    println(s"Successfully created database ${dbResults("name").to[String].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.

    val keyResults = await(adminClient.query(
      CreateKey(Obj("database" -> Database(DB_NAME), "role" -> "server"))
    ))
    val key: String = keyResults("secret").to[String].get
    val client = adminClient.sessionClient(key)

How to create a collection and index

    val SPELLS_COLLECTION = "spells"
    val INDEX_NAME = "spells_index"

    val collectionResults: Value = await(client.query(CreateCollection(Obj("name" -> SPELLS_COLLECTION))))
    println(s"Create Collection for $DB_NAME:\n $collectionResults\n")

    val indexResults: Value = await(client.query(
      CreateIndex(
        Obj("name" -> INDEX_NAME,
          "source" -> Collection(SPELLS_COLLECTION)
        )
      )
    ))
    println(s"Create Index for $DB_NAME:\n $indexResults\n")

How to add entries to a collection

    val addFireResults = await(client.query(
      Create(Collection(Value(SPELLS_COLLECTION)),
        Obj("data" ->
          Obj("name" -> "Fire Beak", "element" -> "water", "cost" -> 15)))
    ))
    println(s"Added spell to collection $SPELLS_COLLECTION: \n $addFireResults \n")

    val addHippoResults = await(client.query(
      Create(Collection(Value(SPELLS_COLLECTION)),
        Obj("data" ->
          Obj("name" -> "Hippo's Wallow", "element" -> "water", "cost" -> 35)))
    ))
    println(s"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 the default method 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 pointer to the document of the collection that was just created.
    val hippoRef = addHippoResults("ref")
    println(s"hippoRef = $hippoRef \n")

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. faunadb.query is a helper package 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");

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
    val name: String = getHippoResults("name").to[String].get
    val cost: Int = getHippoResults("cost").to[Int].get
    val element: String = getHippoResults("element").to[String].get
    println(s"Spell Details: Name=$name, Cost=$cost, Element=$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 if the conversion fails
    val optSpellElement: Option[String] = getHippoResults("element").to[String].toOpt
    optSpellElement match {
      case Some(element2) => println(s"optional spell element $element2")
      case None => 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 faunadb.values.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. faunadb.query is a helper package where you can find all available expressions in the library.

    /*
     * Query for all the spells in the index
     */
    val queryIndexResults: Value = await(client.query(
      SelectAll(Path("data", "id"),
        Paginate(
          Match(Index(Value(INDEX_NAME)))
        )
      )
    ))

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:

    val spellsRefIds: Seq[String] = queryIndexResults.to[Seq[String]].getOrElse(Seq.empty)
    println(s"spellsRefIds = $spellsRefIds \n")

How to work with user defined classes

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

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

 case class Spell(name: String, element: String, cost: Option[Int])

  object Spell {
    implicit val spellCodec: Codec[Spell] = Codec.caseClass[Spell]
  }

Option is used to mark a field that is optional and might not have a value

Encoding and decoding user defined classes

To persist a document of Spell in FaunaDB:

    val newSpell = Spell("Water Dragon's Claw", "water", Option(25))
    val storeSpellResult = await(client.query(
      Create(
        Collection(SPELLS_COLLECTION),
        Obj("data" -> newSpell))
    ))
    println(s"Stored spell:\n $storeSpellResult \n")

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

    val dragonRef = storeSpellResult("ref")
    val getDragonResult = await(client.query(
      Select(
        Value("data"),
        Get(dragonRef)
      )
    ))

    val spell = getDragonResult.to[Spell].get
    println(s"dragon spell: $spell \n")

Example of using a proper Future mapping to handle results async. Could be map, flatMap or a for expression.

    for {
      dragonSpellVal <- client.query(Select(Value("data"), Get(dragonRef)))
      dragonSpell = dragonSpellVal.to[Spell].get
      hippoSpellVal <- client.query(Select(Value("data"), Get(hippoRef)))
      hippoSpell = hippoSpellVal.to[Spell].get
    }
    yield {
      //process all spells retrieved
      println(s"Retrieved spells of $dragonSpell and $hippoSpell")
    }

Encoding and decoding lists of user defined collections

To persist a Scala sequence of Spell to FaunaDB because a codec is defined for the list it can be directly passed to the query Foreach. This will convert it into a Value type:

    val spellOne = Spell("Chill Touch", "ice", Option(18))
    val spellTwo = Spell("Dancing Lights", "fire", Option(45))
    val spellThree = Spell("Fire Bolt", "fire", Option(32))
    val spellList = Seq(spellOne, spellTwo, spellThree)

    //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
    val spellsListSave = await(client.query(
      Foreach(spellList,
        Lambda { nextSpell =>
          Create(
            Collection(Value(SPELLS_COLLECTION)),
            Obj("data" -> nextSpell))
        })
    ))

    println(s"Created list of spells from java list: \n $spellsListSave \n")
    val spellCollection = spellsListSave.to[Seq[Spell]].get
    println(s"saved ${spellCollection.size} spells:")
    spellCollection.foreach((nextSpell: Spell) => println(s"   $nextSpell"))

    println("\n")

Read a list of all Spells out of the Spells index and decode back to a Scala sequence of Spells:

    val findAllSpells = await(client.query(
      SelectAll("data" / "data",
        Map(
          Paginate(Match(Index(Value(INDEX_NAME)))),
          Lambda { x => Get(x) }
        )
      )
    ))

    println(s"findAllSpells = $findAllSpells\n")

    val allSpellsCollection = findAllSpells.to[Seq[Spell]].get
    println(s"read ${allSpellsCollection.size} spells:")
    allSpellsCollection.foreach((nextSpell: Spell) => println(s"   $nextSpell"))