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

swaydb.SwayDB.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2019 Simer Plaha (@simerplaha)
 *
 * This file is a part of SwayDB.
 *
 * SwayDB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * SwayDB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with SwayDB. If not, see .
 */

package swaydb

import com.typesafe.scalalogging.LazyLogging
import java.nio.file.Path
import scala.concurrent.ExecutionContext
import scala.concurrent.duration.{FiniteDuration, _}
import scala.concurrent.forkjoin.ForkJoinPool
import swaydb.core.BlockingCoreAPI
import swaydb.core.data._
import swaydb.core.function.FunctionStore
import swaydb.core.queue.FileLimiter
import swaydb.core.tool.AppendixRepairer
import swaydb.data.{IO, MaxKey}
import swaydb.data.accelerate.Level0Meter
import swaydb.data.compaction.LevelMeter
import swaydb.data.config._
import swaydb.data.order.{KeyOrder, TimeOrder}
import swaydb.data.repairAppendix.RepairResult.OverlappingSegments
import swaydb.data.repairAppendix._
import swaydb.data.slice.Slice
import swaydb.serializers.Serializer

/**
  * Instance used for creating/initialising databases.
  */
object SwayDB extends LazyLogging {

  private implicit val memoryFunctionStore: FunctionStore = FunctionStore.memory()
  private implicit val timeOrder: TimeOrder[Slice[Byte]] = TimeOrder.long

  /**
    * Default execution context for all databases.
    *
    * This can be overridden by provided an implicit parameter in the scope of where the database is initialized.
    */
  def defaultExecutionContext = new ExecutionContext {
    val threadPool = new ForkJoinPool(100)

    def execute(runnable: Runnable) =
      threadPool execute runnable

    def reportFailure(exception: Throwable): Unit =
      logger.error("Execution context failure", exception)
  }

  /**
    * Creates a database based on the input config.
    *
    * @param config                 Configuration to use to create the database
    * @param maxSegmentsOpen        Number of concurrent opened Segments
    * @param cacheSize              Size of in-memory key-values. For Memory database this set the size of uncompressed key-values.
    *                               If compression is used for memory database the this field can be ignored.
    * @param cacheCheckDelay        Sets the max interval at which key-values get dropped from the cache. The delays
    *                               are dynamically adjusted based on the current size of the cache to stay close the set
    *                               cacheSize.
    *                               If compression is not used for memory database the this field can be ignored.
    * @param segmentsOpenCheckDelay For persistent Levels only. This can property is not used for databases.
    *                               Sets the max interval at which Segments get closed. The delays
    *                               are dynamically adjusted based on the current number of open Segments.
    * @param keySerializer          Converts keys to Bytes
    * @param valueSerializer        Converts values to Bytes
    * @param ordering               Sort order for keys
    * @param ec                     ExecutionContext
    * @tparam K Type of key
    * @tparam V Type of value
    * @return Database instance
    */
  def apply[K, V](config: SwayDBPersistentConfig,
                  maxSegmentsOpen: Int,
                  cacheSize: Int,
                  cacheCheckDelay: FiniteDuration,
                  segmentsOpenCheckDelay: FiniteDuration)(implicit keySerializer: Serializer[K],
                                                          valueSerializer: Serializer[V],
                                                          keyOrder: KeyOrder[Slice[Byte]],
                                                          ec: ExecutionContext): IO[swaydb.Map[K, V]] =
    BlockingCoreAPI(
      config = config,
      maxOpenSegments = maxSegmentsOpen,
      cacheSize = cacheSize,
      cacheCheckDelay = cacheCheckDelay,
      segmentsOpenCheckDelay = segmentsOpenCheckDelay
    ) map {
      core =>
        swaydb.Map[K, V](new SwayDB(core))
    }

  def apply[T](config: SwayDBPersistentConfig,
               maxSegmentsOpen: Int,
               cacheSize: Int,
               cacheCheckDelay: FiniteDuration,
               segmentsOpenCheckDelay: FiniteDuration)(implicit serializer: Serializer[T],
                                                       keyOrder: KeyOrder[Slice[Byte]],
                                                       ec: ExecutionContext): IO[swaydb.Set[T]] =
    BlockingCoreAPI(
      config = config,
      maxOpenSegments = maxSegmentsOpen,
      cacheSize = cacheSize,
      cacheCheckDelay = cacheCheckDelay,
      segmentsOpenCheckDelay = segmentsOpenCheckDelay
    ) map {
      core =>
        swaydb.Set[T](new SwayDB(core))
    }

  def apply[K, V](config: SwayDBMemoryConfig,
                  cacheSize: Int,
                  cacheCheckDelay: FiniteDuration)(implicit keySerializer: Serializer[K],
                                                   valueSerializer: Serializer[V],
                                                   keyOrder: KeyOrder[Slice[Byte]],
                                                   ec: ExecutionContext): IO[swaydb.Map[K, V]] =
    BlockingCoreAPI(
      config = config,
      maxOpenSegments = 0,
      cacheSize = cacheSize,
      cacheCheckDelay = cacheCheckDelay,
      segmentsOpenCheckDelay = Duration.Zero
    ) map {
      core =>
        swaydb.Map[K, V](new SwayDB(core))
    }

  def apply[T](config: SwayDBMemoryConfig,
               cacheSize: Int,
               cacheCheckDelay: FiniteDuration)(implicit serializer: Serializer[T],
                                                keyOrder: KeyOrder[Slice[Byte]],
                                                ec: ExecutionContext): IO[swaydb.Set[T]] =
    BlockingCoreAPI(
      config = config,
      maxOpenSegments = 0,
      cacheSize = cacheSize,
      cacheCheckDelay = cacheCheckDelay,
      segmentsOpenCheckDelay = Duration.Zero
    ) map {
      core =>
        swaydb.Set[T](new SwayDB(core))
    }

  def apply[T](config: LevelZeroConfig)(implicit serializer: Serializer[T],
                                        keyOrder: KeyOrder[Slice[Byte]],
                                        ec: ExecutionContext): IO[swaydb.Set[T]] =
    BlockingCoreAPI(
      config = config
    ) map {
      core =>
        swaydb.Set[T](new SwayDB(core))
    }

  private def toCoreFunctionOutput[V](output: swaydb.Apply[V])(implicit valueSerializer: Serializer[V]): SwayFunctionOutput =
    output match {
      case Apply.Nothing =>
        SwayFunctionOutput.Nothing

      case Apply.Remove =>
        SwayFunctionOutput.Remove

      case Apply.Expire(deadline) =>
        SwayFunctionOutput.Expire(deadline)

      case update: Apply.Update[V] =>
        val untypedValue: Slice[Byte] = valueSerializer.write(update.value)
        SwayFunctionOutput.Update(Some(untypedValue), update.deadline)
    }

  private[swaydb] def toCoreFunction[K, V](f: (K, Option[Deadline]) => Apply[V])(implicit keySerializer: Serializer[K],
                                                                                 valueSerializer: Serializer[V]): swaydb.core.data.SwayFunction = {
    import swaydb.serializers._

    def function(key: Slice[Byte], deadline: Option[Deadline]) =
      toCoreFunctionOutput(f(key.read[K], deadline))

    swaydb.core.data.SwayFunction.KeyDeadline(function)
  }

  private[swaydb] def toCoreFunction[K, V](f: (K, V, Option[Deadline]) => Apply[V])(implicit keySerializer: Serializer[K],
                                                                                    valueSerializer: Serializer[V]): swaydb.core.data.SwayFunction = {
    import swaydb.serializers._

    def function(key: Slice[Byte], value: Option[Slice[Byte]], deadline: Option[Deadline]) =
      toCoreFunctionOutput(f(key.read[K], value.read[V], deadline))

    swaydb.core.data.SwayFunction.KeyValueDeadline(function)
  }

  private[swaydb] def toCoreFunction[K, V](f: V => Apply[V])(implicit valueSerializer: Serializer[V]): swaydb.core.data.SwayFunction = {
    import swaydb.serializers._

    def function(value: Option[Slice[Byte]]) =
      toCoreFunctionOutput(f(value.read[V]))

    swaydb.core.data.SwayFunction.Value(function)
  }

  /**
    * Documentation: http://www.swaydb.io/api/repairAppendix
    */
  def repairAppendix[K](levelPath: Path,
                        repairStrategy: AppendixRepairStrategy)(implicit serializer: Serializer[K],
                                                                fileLimiter: FileLimiter,
                                                                keyOrder: KeyOrder[Slice[Byte]] = KeyOrder.default,
                                                                ec: ExecutionContext = defaultExecutionContext): IO[RepairResult[K]] =
  //convert to typed result.
    AppendixRepairer(levelPath, repairStrategy) match {
      case IO.Failure(IO.Error.Fatal(OverlappingSegmentsException(segmentInfo, overlappingSegmentInfo))) =>
        IO.Success(
          OverlappingSegments[K](
            segmentInfo =
              SegmentInfo(
                path = segmentInfo.path,
                minKey = serializer.read(segmentInfo.minKey),
                maxKey =
                  segmentInfo.maxKey match {
                    case MaxKey.Fixed(maxKey) =>
                      MaxKey.Fixed(serializer.read(maxKey))

                    case MaxKey.Range(fromKey, maxKey) =>
                      MaxKey.Range(fromKey = serializer.read(fromKey), maxKey = serializer.read(maxKey))
                  },
                segmentSize = segmentInfo.segmentSize,
                keyValueCount = segmentInfo.keyValueCount
              ),
            overlappingSegmentInfo =
              SegmentInfo(
                path = overlappingSegmentInfo.path,
                minKey = serializer.read(overlappingSegmentInfo.minKey),
                maxKey =
                  overlappingSegmentInfo.maxKey match {
                    case MaxKey.Fixed(maxKey) =>
                      MaxKey.Fixed(serializer.read(maxKey))

                    case MaxKey.Range(fromKey, maxKey) =>
                      MaxKey.Range(fromKey = serializer.read(fromKey), maxKey = serializer.read(maxKey))
                  },
                segmentSize = overlappingSegmentInfo.segmentSize,
                keyValueCount = overlappingSegmentInfo.keyValueCount
              )
          )
        )
      case IO.Failure(error) =>
        IO.Failure(error)

      case IO.Success(_) =>
        IO.Success(RepairResult.Repaired)
    }
}

private[swaydb] class SwayDB(api: BlockingCoreAPI) {

  def put(key: Slice[Byte]) =
    api.put(key)

  def put(key: Slice[Byte], value: Option[Slice[Byte]]): IO[Level0Meter] =
    api.put(key, value)

  def put(key: Slice[Byte], value: Option[Slice[Byte]], expireAt: Deadline): IO[Level0Meter] =
    api.put(key, value, expireAt)

  def update(key: Slice[Byte], value: Option[Slice[Byte]]): IO[Level0Meter] =
    api.update(key, value)

  def update(from: Slice[Byte], to: Slice[Byte], value: Option[Slice[Byte]]): IO[Level0Meter] =
    api.update(from, to, value)

  def expire(key: Slice[Byte], at: Deadline): IO[Level0Meter] =
    api.remove(key, at)

  def expire(from: Slice[Byte], to: Slice[Byte], at: Deadline): IO[Level0Meter] =
    api.remove(from, to, at)

  def remove(key: Slice[Byte]) =
    api.remove(key)

  def remove(from: Slice[Byte], to: Slice[Byte]): IO[Level0Meter] =
    api.remove(from, to)

  def function(key: Slice[Byte], function: Slice[Byte]) =
    api.function(key, function)

  def function(from: Slice[Byte], to: Slice[Byte], function: Slice[Byte]): IO[Level0Meter] =
    api.function(from, to, function)

  def registerFunction(functionID: Slice[Byte], function: swaydb.core.data.SwayFunction): swaydb.core.data.SwayFunction =
    api.registerFunction(functionID, function)

  def commit(prepare: Iterable[Prepare[Slice[Byte], Option[Slice[Byte]]]]) =
    api.put(prepare)

  def head: IO[Option[(Slice[Byte], Option[Slice[Byte]])]] =
    api.head

  def last: IO[Option[(Slice[Byte], Option[Slice[Byte]])]] =
    api.last

  def keyValueCount: IO[Int] =
    api.bloomFilterKeyValueCount

  def contains(key: Slice[Byte]): IO[Boolean] =
    api contains key

  def mightContain(key: Slice[Byte]): IO[Boolean] =
    api mightContain key

  def get(key: Slice[Byte]): IO[Option[Option[Slice[Byte]]]] =
    api.get(key)

  def getKey(key: Slice[Byte]): IO[Option[Slice[Byte]]] =
    api.getKey(key)

  def getKeyValue(key: Slice[Byte]): IO[Option[(Slice[Byte], Option[Slice[Byte]])]] =
    api.getKeyValue(key)

  def beforeKey(key: Slice[Byte]) =
    api.beforeKey(key)

  def before(key: Slice[Byte]) =
    api.before(key)

  def afterKey(key: Slice[Byte]) =
    api.afterKey(key)

  def after(key: Slice[Byte]) =
    api.after(key)

  def headKey: IO[Option[Slice[Byte]]] =
    api.headKey

  def lastKey: IO[Option[Slice[Byte]]] =
    api.lastKey

  def sizeOfSegments: Long =
    api.sizeOfSegments

  def level0Meter: Level0Meter =
    api.level0Meter

  def levelMeter(levelNumber: Int): Option[LevelMeter] =
    api.levelMeter(levelNumber)

  def valueSize(key: Slice[Byte]): IO[Option[Int]] =
    api.valueSize(key)

  def deadline(key: Slice[Byte]): IO[Option[Deadline]] =
    api.deadline(key)

  def close(): IO[Unit] =
    api.close()
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy