All Downloads are FREE. Search and download functionalities are using the official Maven repository.

avokka.arangodb.ArangoDocument.scala Maven / Gradle / Ivy

The newest version!
package avokka.arangodb

import avokka.arangodb.models._
import avokka.arangodb.protocol.{ArangoClient, ArangoResponse}
import avokka.arangodb.types.{DatabaseName, DocumentHandle, TransactionId}
import avokka.velocypack._
import cats.Functor

/**
  * Arango document API
  *
  * @tparam F effect
  * @see [[https://www.arangodb.com/docs/stable/http/document-working-with-documents.html]]
  */
trait ArangoDocument[F[_]] {

  /** document handle */
  def handle: DocumentHandle

  /**
    * Returns the document identified by *document-handle*. The returned document contains three special attributes:
    * *_id* containing the document handle, *_key* containing key which uniquely identifies a document
    * in a given collection and *_rev* containing the revision.
    *
    * @tparam T          The type of the document.
    * @param ifNoneMatch If the "If-None-Match" header is given, then it must contain exactly one
    *                    Etag. The document is returned, if it has a different revision than the
    *                    given Etag. Otherwise an HTTP 304 is returned.
    * @param ifMatch     If the "If-Match" header is given, then it must contain exactly one
    *                    Etag. The document is returned, if it has the same revision as the
    *                    given Etag. Otherwise a HTTP 412 is returned.
    */
  def read[T: VPackDecoder](
      ifNoneMatch: Option[String] = None,
      ifMatch: Option[String] = None,
      transaction: Option[TransactionId] = None,
  ): F[ArangoResponse[T]]

  /**
    * Like read, but only returns the header fields and not the body. You can use this call to get the current revision of a document or check if the document was deleted.
    *
    * @param ifNoneMatch  If the “If-None-Match” header is given, then it must contain exactly one Etag. If the current document revision is not equal to the specified Etag, an HTTP 200 response is returned. If the current document revision is identical to the specified Etag, then an HTTP 304 is returned.
    * @param ifMatch      If the “If-Match” header is given, then it must contain exactly one Etag. The document is returned, if it has the same revision as the given Etag. Otherwise a HTTP 412 is returned.
    */
  def head(
      ifNoneMatch: Option[String] = None,
      ifMatch: Option[String] = None,
      transaction: Option[TransactionId] = None,
  ): F[ArangoResponse.Header]

  /**
    * Removes a document
    * @param waitForSync Wait until deletion operation has been synced to disk.
    * @param returnOld   Return additionally the complete previous revision of the changed
    *                    document under the attribute old in the result.
    * @param silent      If set to true, an empty object will be returned as response. No meta-data
    *                    will be returned for the removed document. This option can be used to
    *                    save some network traffic.
    * @param ifMatch     You can conditionally remove a document based on a target revision id by
    *                    using the if-match HTTP header.
    * @tparam T          Response body type
    */
  def remove[T: VPackDecoder](
      waitForSync: Boolean = false,
      returnOld: Boolean = false,
      silent: Boolean = false,
      ifMatch: Option[String] = None,
      transaction: Option[TransactionId] = None,
  ): F[ArangoResponse[Document[T]]]

  /**
    * @param patch representation of a document update as an object
    * @param keepNull If the intention is to delete existing attributes with the patch command, the URL query parameter *keepNull* can be used with a value of *false*. This will modify the behavior of the patch command to remove any attributes from the existing document that are contained in the patch document with an attribute value of *null*.   (optional)
    * @param mergeObjects Controls whether objects (not arrays) will be merged if present in both the existing and the patch document. If set to *false*, the value in the patch document will overwrite the existing document's value. If set to *true*, objects will be merged. The default is *true*.   (optional)
    * @param waitForSync Wait until document has been synced to disk.   (optional)
    * @param ignoreRevs By default, or if this is set to *true*, the *_rev* attributes in  the given document is ignored. If this is set to *false*, then the *_rev* attribute given in the body document is taken as a precondition. The document is only updated if the current revision is the one specified.   (optional)
    * @param returnOld Return additionally the complete previous revision of the changed  document under the attribute *old* in the result.   (optional)
    * @param returnNew Return additionally the complete new document under the attribute *new* in the result.   (optional)
    * @param silent If set to *true*, an empty object will be returned as response. No meta-data  will be returned for the updated document. This option can be used to save some network traffic.   (optional)
    * @param ifMatch You can conditionally update a document based on a target revision id by using the *if-match* HTTP header.   (optional)
    * @tparam T document type
    * @tparam P patch type
    */
  def update[T: VPackDecoder, P: VPackEncoder](
      patch: P,
      keepNull: Boolean = false,
      mergeObjects: Boolean = true,
      waitForSync: Boolean = false,
      ignoreRevs: Boolean = true,
      returnOld: Boolean = false,
      returnNew: Boolean = false,
      silent: Boolean = false,
      ifMatch: Option[String] = None,
      transaction: Option[TransactionId] = None,
  ): F[ArangoResponse[Document[T]]]

  /**
    * @param document representation of a document update as an object
    * @param waitForSync Wait until document has been synced to disk.   (optional)
    * @param ignoreRevs By default, or if this is set to *true*, the *_rev* attributes in  the given document is ignored. If this is set to *false*, then the *_rev* attribute given in the body document is taken as a precondition. The document is only updated if the current revision is the one specified.   (optional)
    * @param returnOld Return additionally the complete previous revision of the changed  document under the attribute *old* in the result.   (optional)
    * @param returnNew Return additionally the complete new document under the attribute *new* in the result.   (optional)
    * @param silent If set to *true*, an empty object will be returned as response. No meta-data  will be returned for the updated document. This option can be used to save some network traffic.   (optional)
    * @param ifMatch You can conditionally update a document based on a target revision id by using the *if-match* HTTP header.   (optional)
    * @tparam T document type
    */
  def replace[T: VPackEncoder: VPackDecoder](
      document: T,
      waitForSync: Boolean = false,
      ignoreRevs: Boolean = true,
      returnOld: Boolean = false,
      returnNew: Boolean = false,
      silent: Boolean = false,
      ifMatch: Option[String] = None,
      transaction: Option[TransactionId] = None,
  ): F[ArangoResponse[Document[T]]]

  /**
    * build an UPSERT query at key with INSERT+UPDATE from obj
    * @param obj vpack object
    * @return query
    */
  def upsert(obj: VObject): ArangoQuery[F, VObject]
}

object ArangoDocument {

  def apply[F[_]: ArangoClient: Functor](database: DatabaseName, _handle: DocumentHandle): ArangoDocument[F] =
    new ArangoDocument[F] {

      override val handle: DocumentHandle = _handle

      private val path: String = "/_api/document/" + handle.path

      override def read[T: VPackDecoder](
          ifNoneMatch: Option[String],
          ifMatch: Option[String],
          transaction: Option[TransactionId],
      ): F[ArangoResponse[T]] =
        GET(
          database,
          path,
          meta = Map(
            "If-None-Match" -> ifNoneMatch,
            "If-Match" -> ifMatch,
            Transaction.KEY -> transaction.map(_.repr)
          ).collectDefined
        ).execute

      override def head(
          ifNoneMatch: Option[String],
          ifMatch: Option[String],
          transaction: Option[TransactionId],
      ): F[ArangoResponse.Header] =
        HEAD(
          database,
          path,
          meta = Map(
            "If-None-Match" -> ifNoneMatch,
            "If-Match" -> ifMatch,
            Transaction.KEY -> transaction.map(_.repr)
          ).collectDefined
        ).execute

      override def remove[T: VPackDecoder](
          waitForSync: Boolean,
          returnOld: Boolean,
          silent: Boolean,
          ifMatch: Option[String],
          transaction: Option[TransactionId],
      ): F[ArangoResponse[Document[T]]] =
        DELETE(
          database,
          path,
          Map(
            "waitForSync" -> waitForSync.toString,
            "returnOld" -> returnOld.toString,
            "silent" -> silent.toString,
          ),
          Map(
            "If-Match" -> ifMatch,
            Transaction.KEY -> transaction.map(_.repr)
          ).collectDefined
        ).execute

      override def update[T: VPackDecoder, P: VPackEncoder](
          patch: P,
          keepNull: Boolean,
          mergeObjects: Boolean,
          waitForSync: Boolean,
          ignoreRevs: Boolean,
          returnOld: Boolean,
          returnNew: Boolean,
          silent: Boolean,
          ifMatch: Option[String],
          transaction: Option[TransactionId],
      ): F[ArangoResponse[Document[T]]] =
        PATCH(
          database,
          path,
          Map(
            "keepNull" -> keepNull.toString,
            "mergeObjects" -> mergeObjects.toString,
            "waitForSync" -> waitForSync.toString,
            "ignoreRevs" -> ignoreRevs.toString,
            "returnOld" -> returnOld.toString,
            "returnNew" -> returnNew.toString,
            "silent" -> silent.toString,
          ),
          Map(
            "If-Match" -> ifMatch,
            Transaction.KEY -> transaction.map(_.repr)
          ).collectDefined
        ).body(patch).execute

      override def replace[T: VPackEncoder: VPackDecoder](
          document: T,
          waitForSync: Boolean,
          ignoreRevs: Boolean,
          returnOld: Boolean,
          returnNew: Boolean,
          silent: Boolean,
          ifMatch: Option[String],
          transaction: Option[TransactionId],
      ): F[ArangoResponse[Document[T]]] =
        ArangoClient[F].execute(
          PUT(
            database,
            path,
            Map(
              "waitForSync" -> waitForSync.toString,
              "ignoreRevs" -> ignoreRevs.toString,
              "returnOld" -> returnOld.toString,
              "returnNew" -> returnNew.toString,
              "silent" -> silent.toString,
            ),
            Map(
              "If-Match" -> ifMatch,
              Transaction.KEY -> transaction.map(_.repr)
            ).collectDefined
          ).body(document)
        )(
          implicitly[VPackEncoder[T]].mapObject(_.filter(Document.filterEmptyInternalAttributes)),
          implicitly
        )

      override def upsert(obj: VObject): ArangoQuery[F, VObject] = {
        val kvs = obj.values.keys
          .map { key =>
            key + ":@" + key
          }
          .mkString(",")
        ArangoQuery[F, VObject](
          database,
          Query(
            s"UPSERT {_key:@_key} INSERT {_key:@_key,$kvs} UPDATE {$kvs} IN @@collection RETURN NEW",
            obj.updated("@collection", handle.collection).updated("_key", handle.key)
          )
        )
      }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy