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

swaydb.core.Core.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2020 Simer JS Plaha ([email protected] - @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 .
 *
 * Additional permission under the GNU Affero GPL version 3 section 7:
 * If you modify this Program or any covered work, only by linking or combining
 * it with separate works, the licensors of this Program grant you additional
 * permission to convey the resulting work.
 */

package swaydb.core

import java.nio.file.Path

import com.typesafe.scalalogging.LazyLogging
import swaydb.Bag.Implicits._
import swaydb.core.actor.ByteBufferSweeper.ByteBufferSweeperActor
import swaydb.core.build.BuildValidator
import swaydb.core.data.{Memory, SwayFunction, Value}
import swaydb.core.function.FunctionStore
import swaydb.core.level.compaction.Compactor
import swaydb.core.level.compaction.throttle.ThrottleState
import swaydb.core.level.zero.LevelZero
import swaydb.core.map.MapEntry
import swaydb.core.map.serializer.LevelZeroMapEntryWriter
import swaydb.core.map.timer.Timer
import swaydb.core.segment.ThreadReadState
import swaydb.data.NonEmptyList
import swaydb.data.accelerate.LevelZeroMeter
import swaydb.data.compaction.LevelMeter
import swaydb.data.config._
import swaydb.data.order.{KeyOrder, TimeOrder}
import swaydb.data.slice.{Slice, SliceOption}
import swaydb.data.util.TupleOrNone
import swaydb._
import swaydb.data.sequencer.Sequencer

import scala.concurrent.duration._

/**
 * Core defines the interface to SwayDB's internals. User level APIs interact with SwayDB via this instance only
 * and do directly invoke any function on [[LevelZero]] or any other [[swaydb.core.level.Level]].
 */
private[swaydb] object Core {

  val closedMessage = "Cannot perform read or write on a closed instance."

  def apply(enableTimer: Boolean,
            cacheKeyValueIds: Boolean,
            fileCache: FileCache.On,
            memoryCache: MemoryCache,
            threadStateCache: ThreadStateCache,
            config: SwayDBPersistentConfig)(implicit keyOrder: KeyOrder[Slice[Byte]],
                                            timeOrder: TimeOrder[Slice[Byte]],
                                            functionStore: FunctionStore,
                                            buildValidator: BuildValidator): IO[swaydb.Error.Boot, Core[Glass]] =
    CoreInitializer(
      config = config,
      enableTimer = enableTimer,
      cacheKeyValueIds = cacheKeyValueIds,
      fileCache = fileCache,
      threadStateCache = threadStateCache,
      memoryCache = memoryCache
    )

  def apply(enableTimer: Boolean,
            cacheKeyValueIds: Boolean,
            fileCache: FileCache.On,
            memoryCache: MemoryCache,
            threadStateCache: ThreadStateCache,
            config: SwayDBMemoryConfig)(implicit keyOrder: KeyOrder[Slice[Byte]],
                                        timeOrder: TimeOrder[Slice[Byte]],
                                        functionStore: FunctionStore,
                                        buildValidator: BuildValidator): IO[swaydb.Error.Boot, Core[Glass]] =
    CoreInitializer(
      config = config,
      enableTimer = enableTimer,
      cacheKeyValueIds = cacheKeyValueIds,
      fileCache = fileCache,
      threadStateCache = threadStateCache,
      memoryCache = memoryCache
    )

  /**
   * Converts all prepare statement to a single transactional commit entry ([[MapEntry]]).
   */
  private def prepareToMapEntry(entries: Iterator[Prepare[Slice[Byte], SliceOption[Byte], Slice[Byte]]])(timer: Timer): Option[MapEntry[Slice[Byte], Memory]] =
    entries.foldLeft(Option.empty[MapEntry[Slice[Byte], Memory]]) {
      case (mapEntry, prepare) =>
        val nextEntry =
          prepare match {
            case Prepare.Put(key, value, expire) =>
              if (key.isEmpty) throw new IllegalArgumentException("Key cannot be empty.")

              MapEntry.Put[Slice[Byte], Memory.Put](key, Memory.Put(key, value, expire, timer.next))(LevelZeroMapEntryWriter.Level0PutWriter)

            case Prepare.Add(key, expire) =>
              if (key.isEmpty) throw new IllegalArgumentException("Key cannot be empty.")

              MapEntry.Put[Slice[Byte], Memory.Put](key, Memory.Put(key, Slice.Null, expire, timer.next))(LevelZeroMapEntryWriter.Level0PutWriter)

            case Prepare.Remove(key, toKey, expire) =>
              if (key.isEmpty) throw new IllegalArgumentException("Key cannot be empty.")
              if (toKey.exists(_.isEmpty)) throw new IllegalArgumentException("toKey cannot be empty.")

              toKey map {
                toKey =>
                  (MapEntry.Put[Slice[Byte], Memory.Range](key, Memory.Range(key, toKey, Value.FromValue.Null, Value.Remove(expire, timer.next)))(LevelZeroMapEntryWriter.Level0RangeWriter): MapEntry[Slice[Byte], Memory]) ++
                    MapEntry.Put[Slice[Byte], Memory.Remove](toKey, Memory.Remove(toKey, expire, timer.next))(LevelZeroMapEntryWriter.Level0RemoveWriter)
              } getOrElse {
                MapEntry.Put[Slice[Byte], Memory.Remove](key, Memory.Remove(key, expire, timer.next))(LevelZeroMapEntryWriter.Level0RemoveWriter)
              }

            case Prepare.Update(key, toKey, value) =>
              if (key.isEmpty) throw new IllegalArgumentException("Key cannot be empty.")
              if (toKey.exists(_.isEmpty)) throw new IllegalArgumentException("toKey cannot be empty.")

              toKey map {
                toKey =>
                  (MapEntry.Put[Slice[Byte], Memory.Range](key, Memory.Range(key, toKey, Value.FromValue.Null, Value.Update(value, None, timer.next)))(LevelZeroMapEntryWriter.Level0RangeWriter): MapEntry[Slice[Byte], Memory]) ++
                    MapEntry.Put[Slice[Byte], Memory.Update](toKey, Memory.Update(toKey, value, None, timer.next))(LevelZeroMapEntryWriter.Level0UpdateWriter)
              } getOrElse {
                MapEntry.Put[Slice[Byte], Memory.Update](key, Memory.Update(key, value, None, timer.next))(LevelZeroMapEntryWriter.Level0UpdateWriter)
              }

            case Prepare.ApplyFunction(key, toKey, function) =>
              if (key.isEmpty) throw new IllegalArgumentException("Key cannot be empty.")
              if (toKey.exists(_.isEmpty)) throw new IllegalArgumentException("toKey cannot be empty.")

              toKey map {
                toKey =>
                  (MapEntry.Put[Slice[Byte], Memory.Range](key, Memory.Range(key, toKey, Value.FromValue.Null, Value.Function(function, timer.next)))(LevelZeroMapEntryWriter.Level0RangeWriter): MapEntry[Slice[Byte], Memory]) ++
                    MapEntry.Put[Slice[Byte], Memory.Function](toKey, Memory.Function(toKey, function, timer.next))(LevelZeroMapEntryWriter.Level0FunctionWriter)
              } getOrElse {
                MapEntry.Put[Slice[Byte], Memory.Function](key, Memory.Function(key, function, timer.next))(LevelZeroMapEntryWriter.Level0FunctionWriter)
              }
          }
        Some(mapEntry.map(_ ++ nextEntry) getOrElse nextEntry)
    }
}

private[swaydb] class Core[BAG[_]](private val zero: LevelZero,
                                   coreState: CoreState,
                                   threadStateCache: ThreadStateCache,
                                   private val sequencer: Sequencer[BAG],
                                   val readStates: ThreadLocal[ThreadReadState])(implicit bag: Bag[BAG],
                                                                                 compactors: NonEmptyList[ActorWire[Compactor[ThrottleState], ThrottleState]],
                                                                                 private[swaydb] val bufferSweeper: ByteBufferSweeperActor) extends LazyLogging {

  def zeroPath: Path =
    zero.path

  @inline private def assertTerminated[T, BAG2[_]](f: => BAG2[T])(implicit bag: Bag[BAG2]): BAG2[T] =
    if (coreState.isRunning)
      f
    else
      bag.failure(new IllegalAccessException(Core.closedMessage))

  @inline private def execute[R, BAG[_]](thunk: LevelZero => R)(implicit bag: Bag[BAG]): BAG[R] =
    if (coreState.isRunning)
      try
        bag.success(thunk(zero))
      catch {
        case throwable: Throwable =>
          val error = IO.ExceptionHandler.toError[swaydb.Error.Level](throwable)
          IO.Defer[swaydb.Error.Level, R](thunk(zero), error).run(1)
      }
    else
      bag.failure(new IllegalAccessException(Core.closedMessage))

  def put(key: Slice[Byte]): BAG[OK] =
    assertTerminated(sequencer.execute(zero.put(key)))

  def put(key: Slice[Byte], value: Slice[Byte]): BAG[OK] =
    assertTerminated(sequencer.execute(zero.put(key, value)))

  def put(key: Slice[Byte], value: SliceOption[Byte]): BAG[OK] =
    assertTerminated(sequencer.execute(zero.put(key, value)))

  def put(key: Slice[Byte], value: SliceOption[Byte], removeAt: Deadline): BAG[OK] =
    assertTerminated(sequencer.execute(zero.put(key, value, removeAt)))

  /**
   * Each [[Prepare]] requires a new next [[swaydb.core.data.Time]] for cases where a batch contains overriding keys.
   *
   * Same time indicates that the later Prepare in this batch with the same time as newer Prepare has already applied
   * to the newer prepare therefore ignoring the newer prepare.
   *
   * @note If the default time order [[TimeOrder.long]] is used
   *       Times should always be unique and in incremental order for *ALL* key values.
   */
  def commit(entries: Iterator[Prepare[Slice[Byte], SliceOption[Byte], Slice[Byte]]]): BAG[OK] =
    assertTerminated {
      if (entries.isEmpty)
        bag.failure(new IllegalArgumentException("Cannot write empty batch"))
      else
        sequencer.execute(zero.put(Core.prepareToMapEntry(entries)(_).get)) //Gah .get!
    }

  def remove(key: Slice[Byte]): BAG[OK] =
    assertTerminated(sequencer.execute(zero.remove(key)))

  def expire(key: Slice[Byte], at: Deadline): BAG[OK] =
    assertTerminated(sequencer.execute(zero.remove(key, at)))

  def remove(from: Slice[Byte], to: Slice[Byte]): BAG[OK] =
    assertTerminated(sequencer.execute(zero.remove(from, to)))

  def expire(from: Slice[Byte], to: Slice[Byte], at: Deadline): BAG[OK] =
    assertTerminated(sequencer.execute(zero.remove(from, to, at)))

  def update(key: Slice[Byte], value: Slice[Byte]): BAG[OK] =
    assertTerminated(sequencer.execute(zero.update(key, value)))

  def update(key: Slice[Byte], value: SliceOption[Byte]): BAG[OK] =
    assertTerminated(sequencer.execute(zero.update(key, value)))

  def update(fromKey: Slice[Byte], to: Slice[Byte], value: Slice[Byte]): BAG[OK] =
    assertTerminated(sequencer.execute(zero.update(fromKey, to, value)))

  def update(fromKey: Slice[Byte], to: Slice[Byte], value: SliceOption[Byte]): BAG[OK] =
    assertTerminated(sequencer.execute(zero.update(fromKey, to, value)))

  def applyFunction(key: Slice[Byte], function: Slice[Byte]): BAG[OK] =
    assertTerminated(sequencer.execute(zero.applyFunction(key, function)))

  def applyFunction(from: Slice[Byte], to: Slice[Byte], function: Slice[Byte]): BAG[OK] =
    assertTerminated(sequencer.execute(zero.applyFunction(from, to, function)))

  def registerFunction(functionId: Slice[Byte], function: SwayFunction): BAG[OK] =
    execute(_.registerFunction(functionId, function))

  def head[BAG[_]](readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[TupleOrNone[Slice[Byte], SliceOption[Byte]]] =
    execute(_.head(readState).toTupleOrNone)

  def headKey[BAG[_]](readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[SliceOption[Byte]] =
    execute(_.headKey(readState))

  def last[BAG[_]](readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[TupleOrNone[Slice[Byte], SliceOption[Byte]]] =
    execute(_.last(readState).toTupleOrNone)

  def lastKey[BAG[_]](readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[SliceOption[Byte]] =
    execute(_.lastKey(readState))

  def bloomFilterKeyValueCount: BAG[Int] =
    execute(_.keyValueCount)

  def deadline(key: Slice[Byte],
               readState: ThreadReadState): BAG[Option[Deadline]] =
    execute(_.deadline(key, readState))

  def sizeOfSegments: Long =
    zero.sizeOfSegments

  def contains(key: Slice[Byte],
               readState: ThreadReadState): BAG[Boolean] =
    execute(_.contains(key, readState))

  def mightContainKey(key: Slice[Byte]): BAG[Boolean] =
    execute(_.mightContainKey(key))

  def mightContainFunction(functionId: Slice[Byte]): BAG[Boolean] =
    execute(_.mightContainFunction(functionId))

  def get(key: Slice[Byte],
          readState: ThreadReadState): BAG[Option[SliceOption[Byte]]] =
    execute(_.get(key, readState).getValue)

  def getKey[BAG[_]](key: Slice[Byte],
                     readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[SliceOption[Byte]] =
    execute(_.getKey(key, readState))

  def getKeyValue[BAG[_]](key: Slice[Byte],
                          readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[TupleOrNone[Slice[Byte], SliceOption[Byte]]] =
    execute(_.get(key, readState).toTupleOrNone)

  def getKeyValueDeadline[BAG[_]](key: Slice[Byte],
                                  readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[TupleOrNone[(Slice[Byte], SliceOption[Byte]), Option[Deadline]]] =
    execute(_.get(key, readState).toKeyValueDeadlineOrNone)

  def getKeyDeadline[BAG[_]](key: Slice[Byte],
                             readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[TupleOrNone[Slice[Byte], Option[Deadline]]] =
    execute(_.get(key, readState).toDeadlineOrNone)

  def before[BAG[_]](key: Slice[Byte],
                     readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[TupleOrNone[Slice[Byte], SliceOption[Byte]]] =
    execute(_.lower(key, readState).toTupleOrNone)

  def beforeKey[BAG[_]](key: Slice[Byte],
                        readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[SliceOption[Byte]] =
    execute(_.lower(key, readState).getKey)

  def after[BAG[_]](key: Slice[Byte],
                    readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[TupleOrNone[Slice[Byte], SliceOption[Byte]]] =
    execute(_.higher(key, readState).toTupleOrNone)

  def afterKey[BAG[_]](key: Slice[Byte],
                       readState: ThreadReadState)(implicit bag: Bag[BAG]): BAG[SliceOption[Byte]] =
    execute(_.higher(key, readState).getKey)

  def valueSize(key: Slice[Byte],
                readState: ThreadReadState): BAG[Option[Int]] =
    execute(_.valueSize(key, readState))

  def levelZeroMeter: LevelZeroMeter =
    zero.levelZeroMeter

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

  def clear(readState: ThreadReadState): BAG[OK] =
    execute(_.clear(readState))

  def clearAppliedFunctions(): BAG[Iterable[String]] =
    execute(_.clearAppliedFunctions())

  def clearAppliedAndRegisteredFunctions(): BAG[Iterable[String]] =
    execute(_.clearAppliedAndRegisteredFunctions())

  def isFunctionApplied(functionId: Slice[Byte]): Boolean =
    zero.isFunctionApplied(functionId)

  def close(): BAG[Unit] =
    closeWithBag()(this.bag)

  def closeWithBag[BAG[_]]()(implicit bag: Bag[BAG]): BAG[Unit] =
    if (coreState.isNotRunning)
      bag.unit
    else
      bag
        .suspend {
          logger.info("********* Shutting down *********")
          coreState setState CoreState.Closing
          sequencer.terminate()
        }
        .andThen {
          compactors.foldLeft(bag.unit) {
            case (result, compactor) =>
              result and compactor.terminateAndClear()
          }
        }
        .andTransform {
          logger.info("Compaction terminated!")
        }
        .and {
          IO.Defer(zero.close()).run(0)
        }
        .andTransform {
          coreState setState CoreState.Closed
        }

  def delete(): BAG[Unit] =
    close().and(zero.delete())

  def state: CoreState.State =
    coreState.getState

  def toBag[BAG2[_]](implicit bag2: Bag[BAG2]): Core[BAG2] =
    new Core[BAG2](
      zero = zero,
      coreState = coreState,
      threadStateCache = threadStateCache,
      sequencer = Sequencer.transfer[BAG, BAG2](sequencer),
      readStates = readStates,
    )(bag = bag2,
      compactors = compactors,
      bufferSweeper = bufferSweeper)

  def toBag[BAG2[_]](serialOrNull: Sequencer[BAG2])(implicit bag2: Bag[BAG2]): Core[BAG2] =
    new Core[BAG2](
      zero = zero,
      coreState = coreState,
      threadStateCache = threadStateCache,
      sequencer = if (serialOrNull == null) Sequencer.transfer[BAG, BAG2](sequencer) else serialOrNull,
      readStates = readStates,
    )(bag = bag2,
      compactors = compactors,
      bufferSweeper = bufferSweeper)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy