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

zio.lmdb.LMDB.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2023 David Crosson
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package zio.lmdb

import zio._
import zio.json._
import zio.stream.ZStream
import zio.config._

trait LMDB {

  def databasePath: String

  def platformCheck(): IO[StorageSystemError, Unit]

  def collectionsAvailable(): IO[StorageSystemError, List[CollectionName]]

  def collectionExists(name: CollectionName): IO[StorageSystemError, Boolean]

  def collectionCreate[T](name: CollectionName, failIfExists: Boolean = true)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[CreateErrors, LMDBCollection[T]]

  def collectionAllocate(name: CollectionName): IO[CreateErrors, Unit]

  def collectionGet[T](name: CollectionName)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[GetErrors, LMDBCollection[T]]

  def collectionSize(name: CollectionName): IO[SizeErrors, Long]

  def collectionClear(name: CollectionName): IO[ClearErrors, Unit]

  def collectionDrop(name: CollectionName): IO[DropErrors, Unit]

  def fetch[T](collectionName: CollectionName, key: RecordKey)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[FetchErrors, Option[T]]

  def head[T](collectionName: CollectionName)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[FetchErrors, Option[(RecordKey, T)]]

  def previous[T](collectionName: CollectionName, beforeThatKey: RecordKey)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[FetchErrors, Option[(RecordKey, T)]]

  def next[T](collectionName: CollectionName, afterThatKey: RecordKey)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[FetchErrors, Option[(RecordKey, T)]]

  def last[T](collectionName: CollectionName)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[FetchErrors, Option[(RecordKey, T)]]

  def contains(collectionName: CollectionName, key: RecordKey): IO[ContainsErrors, Boolean]

  def update[T](collectionName: CollectionName, key: RecordKey, modifier: T => T)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[UpdateErrors, Option[T]]

  def upsert[T](collectionName: CollectionName, key: RecordKey, modifier: Option[T] => T)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[UpsertErrors, T]

  def upsertOverwrite[T](collectionName: CollectionName, key: RecordKey, document: T)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[UpsertErrors, Unit]

  def delete[T](collectionName: CollectionName, key: RecordKey)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[DeleteErrors, Option[T]]

  def collect[T](
    collectionName: CollectionName,
    keyFilter: RecordKey => Boolean = _ => true,
    valueFilter: T => Boolean = (_: T) => true,
    startAfter: Option[RecordKey] = None,
    backward: Boolean = false,
    limit: Option[Int] = None
  )(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): IO[CollectErrors, List[T]]

  def stream[T](
    collectionName: CollectionName,
    keyFilter: RecordKey => Boolean = _ => true,
    startAfter: Option[RecordKey] = None,
    backward: Boolean = false
  )(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZStream[Any, StreamErrors, T]

  def streamWithKeys[T](
    collectionName: CollectionName,
    keyFilter: RecordKey => Boolean = _ => true,
    startAfter: Option[RecordKey] = None,
    backward: Boolean = false
  )(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZStream[Any, StreamErrors, (RecordKey, T)]
}

object LMDB {

  val config: Config[LMDBConfig] = ((Config.string("name").withDefault(LMDBConfig.default.databaseName)
    ?? "Database name, which will be also used as the directory name") ++
    (Config.string("home").optional.withDefault(LMDBConfig.default.databasesHome)
      ?? "Where to store the database directory") ++
    (Config.boolean("sync").withDefault(LMDBConfig.default.fileSystemSynchronized)
      ?? "Synchronize the file system with all database write operations") ++
    (Config.int("maxReaders").withDefault(LMDBConfig.default.maxReaders)
      ?? "The maximum number of readers") ++
    (Config.int("maxCollections").withDefault(LMDBConfig.default.maxCollections)
      ?? "The maximum number of collections which can be created") ++
    (Config.bigInt("mapSize").withDefault(LMDBConfig.default.mapSize)
      ?? "The maximum size of the whole database including metadata"))
    .to[LMDBConfig]
    .nested("lmdb")

  /** Default live implementation using the current configuration provider
    */
  val live: ZLayer[Scope, Any, LMDB] = ZLayer.fromZIO(
    for {
      config <- ZIO.config(LMDB.config)
      // doc     = generateDocs(LMDB.config).toTable.toGithubFlavouredMarkdown
      // _      <- ZIO.logInfo(s"Configuration documentation:\n$doc")
      _      <- ZIO.logInfo(s"Configuration : $config")
      lmdb   <- LMDBLive.setup(config)
    } yield lmdb
  )

  /** Default live implementation using the current configuration provider but overriding any configured database name with the provided one
    * @param name
    *   database name to use
    * @return
    */
  def liveWithDatabaseName(name: String): ZLayer[Scope, Any, LMDB] = ZLayer.fromZIO(
    for {
      config <- ZIO.config(LMDB.config).map(_.copy(databaseName = name))
      // doc     = generateDocs(LMDB.config).toTable.toGithubFlavouredMarkdown
      // _      <- Console.printLine(s"Configuration documentation:\n$doc")
      _      <- ZIO.logInfo(s"Configuration : $config")
      lmdb   <- LMDBLive.setup(config)
    } yield lmdb
  )

  /** Get the used storage directory in your file system.
    *
    * @return
    *   storage directory path
    */
  def databasePath: ZIO[LMDB, StorageSystemError, String] = ZIO.serviceWith(_.databasePath)

  /** Check LMDB server current configuration compatibility
    */
  def platformCheck(): ZIO[LMDB, StorageSystemError, Unit] = ZIO.serviceWithZIO(_.platformCheck())

  /** List all available collections
    *
    * @return
    *   the list of collection names
    */
  def collectionsAvailable(): ZIO[LMDB, StorageSystemError, List[CollectionName]] = ZIO.serviceWithZIO(_.collectionsAvailable())

  /** check if a collection exists
    *
    * @param name
    *   the collection name
    * @return
    *   true if the collection exists
    */
  def collectionExists(name: CollectionName): ZIO[LMDB, StorageSystemError, Boolean] = ZIO.serviceWithZIO(_.collectionExists(name))

  /** Create a collection and return the collection helper facade. Use collection helper facade when all records are using the same json data type.
    *
    * @param name
    *   the collection name
    * @param failIfExists
    *   raise an error if the collection already exists, default to true
    * @tparam T
    *   the data type of the records which must be Json serializable
    * @return
    *   the collection helper facade
    */
  def collectionCreate[T](name: CollectionName, failIfExists: Boolean = true)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, CreateErrors, LMDBCollection[T]] = ZIO.serviceWithZIO(_.collectionCreate(name, failIfExists))

  /** Create a collection
    *
    * @param name
    *   the collection name
    */
  def collectionAllocate(name: CollectionName): ZIO[LMDB, CreateErrors, Unit] = ZIO.serviceWithZIO(_.collectionAllocate(name))

  /** Get a collection helper facade. Use collection helper facade when all records are using the same json data type.
    *
    * @param name
    *   the collection name
    * @return
    *   the collection helper facade
    */
  def collectionGet[T](name: CollectionName)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, GetErrors, LMDBCollection[T]] = ZIO.serviceWithZIO(_.collectionGet(name))

  /** Get how many items a collection contains
    *
    * @param name
    *   the collection name
    * @tparam T
    *   the data type of the records which must be Json serializable
    * @return
    *   the collection size
    */
  def collectionSize(name: CollectionName): ZIO[LMDB, SizeErrors, Long] = ZIO.serviceWithZIO(_.collectionSize(name))

  /** Remove all the content of a collection
    *
    * @param name
    *   the collection name
    */
  def collectionClear(name: CollectionName): ZIO[LMDB, ClearErrors, Unit] = ZIO.serviceWithZIO(_.collectionClear(name))

  /** Drop a collection
    *
    * @param name
    *   the collection name
    */
  def collectionDrop(name: CollectionName): ZIO[LMDB, DropErrors, Unit] = ZIO.serviceWithZIO(_.collectionDrop(name))

  /** Get a collection record
    *
    * @param collectionName
    *   the collection name
    * @param key
    *   the key of the record to get
    * @tparam T
    *   the data type of the record which must be Json serializable
    * @return
    *   some record or none if no record has been found for the given key
    */
  def fetch[T](collectionName: CollectionName, key: RecordKey)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, FetchErrors, Option[T]] = ZIO.serviceWithZIO(_.fetch(collectionName, key))

  /** Get collection first record
    *
    * @param collectionName
    *   the collection name
    * @tparam T
    *   the data type of the record which must be Json serializable
    * @return
    *   some (key,record) tuple or none if the collection is empty
    */
  def head[T](collectionName: CollectionName)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, FetchErrors, Option[(RecordKey, T)]] = ZIO.serviceWithZIO(_.head(collectionName))

  /** Get the previous record for the given key
    *
    * @param collectionName
    *   the collection name
    * @param beforeThatKey
    *   the key of the reference record
    * @tparam T
    *   the data type of the record which must be Json serializable
    * @return
    *   some (key,record) tuple or none if the key is the first one
    */
  def previous[T](collectionName: CollectionName, beforeThatKey: RecordKey)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, FetchErrors, Option[(RecordKey, T)]] = ZIO.serviceWithZIO(_.previous(collectionName, beforeThatKey))

  /** Get the next record for the given key
    *
    * @param collectionName
    *   the collection name
    * @param afterThatKey
    *   the key of the reference record
    * @tparam T
    *   the data type of the record which must be Json serializable
    * @return
    *   some (key,record) tuple or none if the key is the last one
    */
  def next[T](collectionName: CollectionName, afterThatKey: RecordKey)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, FetchErrors, Option[(RecordKey, T)]] = ZIO.serviceWithZIO(_.next(collectionName, afterThatKey))

  /** Get collection last record
    *
    * @param collectionName
    *   the collection name
    * @tparam T
    *   the data type of the record which must be Json serializable
    * @return
    *   some (key,record) tuple or none if the collection is empty
    */
  def last[T](collectionName: CollectionName)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, FetchErrors, Option[(RecordKey, T)]] = ZIO.serviceWithZIO(_.last(collectionName))

  /** Check if a collection contains the given key
    *
    * @param collectionName
    *   the collection name
    * @param key
    *   the key of the record to look for
    * @return
    *   true if the key is used by the given collection
    */
  def contains(collectionName: CollectionName, key: RecordKey): ZIO[LMDB, ContainsErrors, Boolean] = ZIO.serviceWithZIO(_.contains(collectionName, key))

  /** update atomically a record in a collection.
    *
    * @param collectionName
    *   the collection name
    * @param key
    *   the key for the record upsert
    * @param modifier
    *   the lambda used to update the record content
    * @tparam T
    *   the data type of the record which must be Json serializable
    * @returns
    *   the updated record if a record exists for the given key
    */

  def update[T](collectionName: CollectionName, key: RecordKey, modifier: T => T)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, UpdateErrors, Option[T]] = {
    ZIO.serviceWithZIO(_.update[T](collectionName, key, modifier))
  }

  /** update or insert atomically a record in a collection.
    *
    * @param collectionName
    *   the collection name
    * @param key
    *   the key for the record upsert
    * @param modifier
    *   the lambda used to update the record content
    * @tparam T
    *   the data type of the record which must be Json serializable
    * @returns
    *   the updated or inserted record
    */
  def upsert[T](collectionName: CollectionName, key: RecordKey, modifier: Option[T] => T)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, UpsertErrors, T] = {
    ZIO.serviceWithZIO(_.upsert[T](collectionName, key, modifier))
  }

  /** Overwrite or insert a record in a collection. If the key is already being used for a record then the previous record will be overwritten by the new one.
    *
    * @param collectionName
    *   the collection name
    * @param key
    *   the key for the record upsert
    * @param document
    *   the record content to upsert
    * @tparam T
    *   the data type of the record which must be Json serializable
    */
  def upsertOverwrite[T](collectionName: CollectionName, key: RecordKey, document: T)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, UpsertErrors, Unit] =
    ZIO.serviceWithZIO(_.upsertOverwrite[T](collectionName, key, document))

  /** Delete a record in a collection
    *
    * @param collectionName
    *   the collection name
    * @param key
    *   the key of the record to delete
    * @tparam T
    *   the data type of the record which must be Json serializable the deleted record
    * @return
    *   the deleted content
    */
  def delete[T](collectionName: CollectionName, key: RecordKey)(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, DeleteErrors, Option[T]] =
    ZIO.serviceWithZIO(_.delete[T](collectionName, key))

  /** Collect collection content into the memory, use keyFilter or valueFilter to limit the amount of loaded entries.
    *
    * @param collectionName
    *   the collection name
    * @param keyFilter
    *   filter lambda to select only the keys you want, default is no filter, the value deserialization is done **after** the filtering step
    * @param valueFilter
    *   filter lambda to select only the record your want, default is no filter
    * @param startAfter
    *   start the stream after the given key, default is start from the beginning (when backward is false) or from end (when backward is true)
    * @param backward
    *   going in reverse key order, default is false
    * @param limit
    *   maximum number of item you want to get
    * @tparam T
    *   the data type of the record which must be Json serializable
    * @return
    *   All matching records
    */
  def collect[T](
    collectionName: CollectionName,
    keyFilter: RecordKey => Boolean = _ => true,
    valueFilter: T => Boolean = (_: T) => true,
    startAfter: Option[RecordKey] = None,
    backward: Boolean = false,
    limit: Option[Int] = None
  )(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZIO[LMDB, CollectErrors, List[T]] =
    ZIO.serviceWithZIO(_.collect[T](collectionName, keyFilter, valueFilter, startAfter, backward, limit))

  /** Stream collection records, use keyFilter to apply filtering before record deserialization.
    *
    * @param collectionName
    *   the collection name
    * @param keyFilter
    *   filter lambda to select only the keys you want, default is no filter, the value deserialization is done **after** the filtering step
    * @param startAfter
    *   start the stream after the given key, default is start from the beginning (when backward is false) or from end (when backward is true)
    * @param backward
    *   going in reverse key order, default is false
    * @tparam T
    *   the data type of the record which must be Json serializable
    * @return
    *   the stream of records
    */
  def stream[T](
    collectionName: CollectionName,
    keyFilter: RecordKey => Boolean = _ => true,
    startAfter: Option[RecordKey] = None,
    backward: Boolean = false
  )(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZStream[LMDB, StreamErrors, T] =
    ZStream.serviceWithStream(_.stream(collectionName, keyFilter, startAfter, backward))

  /** stream collection Key/record tuples, use keyFilter to apply filtering before record deserialization.
    *
    * @param collectionName
    *   the collection name
    * @param keyFilter
    *   filter lambda to select only the keys you want, default is no filter, the value deserialization is done **after** the filtering step
    * @param startAfter
    *   start the stream after the given key, default is start from the beginning (when backward is false) or from end (when backward is true)
    * @param backward
    *   going in reverse key order, default is false
    * @tparam T
    *   the data type of the record which must be Json serializable
    * @return
    *   the tuple of key and record stream
    */
  def streamWithKeys[T](
    collectionName: CollectionName,
    keyFilter: RecordKey => Boolean = _ => true,
    startAfter: Option[RecordKey] = None,
    backward: Boolean = false
  )(implicit je: JsonEncoder[T], jd: JsonDecoder[T]): ZStream[LMDB, StreamErrors, (RecordKey, T)] =
    ZStream.serviceWithStream(_.streamWithKeys(collectionName, keyFilter, startAfter, backward))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy