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

swaydb.core.map.MapEntry.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.map

import swaydb.core.data.Memory
import swaydb.core.map.MapEntry.{Put, Remove}
import swaydb.core.map.serializer.{MapCodec, MapEntryWriter}
import swaydb.core.util.skiplist.{SkipList, SkipListBatchable, SkipListConcurrent}
import swaydb.data.order.KeyOrder
import swaydb.data.slice.Slice

import scala.collection.mutable.ListBuffer

/**
 * [[MapEntry]]s can be batched via ++ function.
 *
 * Batched MapEntries are checksum and persisted as one batch operation.
 *
 * Batched MapEntries mutable (ListBuffer) to speed up boot-up time for Map recovery.
 * It should be changed to immutable List. Need to fix this.
 *
 * @tparam K Key type
 * @tparam V Value type
 */
private[swaydb] sealed trait MapEntry[K, +V] { thisEntry =>

  def applyBatch[T >: V](skipList: SkipListBatchable[_, _, K, T]): Unit

  def hasRange: Boolean
  def hasUpdate: Boolean
  def hasRemoveDeadline: Boolean

  def entriesCount: Int

  /**
   * Each map entry computes the bytes required for the entry on creation.
   * The total of all _entries are added to compute the file size of the Byte array to be persisted.
   *
   * This ensures that only single iteration will be required to create the final Byte array.
   */
  def entryBytesSize: Int

  def totalByteSize: Int =
    entryBytesSize + MapCodec.headerSize

  def writeTo(slice: Slice[Byte]): Unit

  protected val _entries: ListBuffer[MapEntry.Point[K, _]]

  def asString(keyParser: K => String, valueParser: V => String): String = {
    this match {
      case Put(key, value) =>
        s"""
           |Type         : Add
           |key          : ${keyParser(key)}
           |Value        : ${valueParser(value)}
       """.stripMargin

      case Remove(key) =>
        s"""
           |Type          : Remove
           |key           : ${keyParser(key)}
       """.stripMargin
    }
  }
}

private[swaydb] case object MapEntry {

  val emptyListMemory = List.empty[MapEntry[Slice[Byte], Memory]]

  /**
   * Returns a combined Entry with duplicates removed from oldEntry, favouring newer duplicate entries.
   */
  def distinct[V](newEntry: MapEntry[Slice[Byte], V],
                  oldEntry: MapEntry[Slice[Byte], V])(implicit keyOrder: KeyOrder[Slice[Byte]]): MapEntry[Slice[Byte], V] = {
    import keyOrder._

    val olderEntriesUnique =
      oldEntry.entries filterNot {
        case MapEntry.Put(oldKey, _) =>
          newEntry.entries.exists {
            case MapEntry.Put(newKey, _) =>
              newKey equiv oldKey

            case MapEntry.Remove(newKey) =>
              newKey equiv oldKey
          }

        case MapEntry.Remove(oldKey) =>
          newEntry.entries.exists {
            case MapEntry.Put(newKey, _) =>
              newKey equiv oldKey

            case MapEntry.Remove(newKey) =>
              newKey equiv oldKey
          }
      }

    olderEntriesUnique.foldLeft(newEntry) {
      case (newEntry, oldEntry) =>
        newEntry ++ {
          oldEntry match {
            case entry @ MapEntry.Put(_, _) =>
              entry.copySingle()

            case entry @ MapEntry.Remove(_) =>
              entry.copySingle()
          }
        }
    }
  }

  implicit class MapEntriesBatch[K, V](left: MapEntry[K, V]) {
    def ++(right: MapEntry[K, V]): MapEntry[K, V] =
      new Batch[K, V] {

        override protected val _entries: ListBuffer[MapEntry.Point[K, _]] =
          left._entries ++= right._entries

        override val entryBytesSize: Int =
          left.entryBytesSize + right.entryBytesSize

        override def writeTo(slice: Slice[Byte]): Unit =
          _entries foreach (_.writeTo(slice))

        override def asString(keyParser: K => String, valueParser: V => String): String =
          s"""${left.asString(keyParser, valueParser)}${right.asString(keyParser, valueParser)}"""

        //        override def applyTo[T >: V](skipList: ConcurrentSkipList[K, T]): Unit =
        //          _entries.asInstanceOf[ListBuffer[MapEntry[K, V]]] foreach (_.applyTo(skipList))

        override def applyBatch[T >: V](skipList: SkipListBatchable[_, _, K, T]): Unit = {
          val entries = _entries.asInstanceOf[ListBuffer[MapEntry.Point[K, V]]]

          if (entries.size <= 1)
            entries.headOption foreach {
              case MapEntry.Put(key, value) =>
                skipList.put(key, value)

              case MapEntry.Remove(key) =>
                skipList.remove(key)
            }
          else
            skipList batch {
              skipList: SkipListConcurrent[_, _, K, T] =>
                entries map {
                  case MapEntry.Put(key, value) =>
                    skipList.put(key, value)

                  case MapEntry.Remove(key) =>
                    skipList.remove(key)
                }
            }
        }

        override val hasRange: Boolean =
          left.hasRange || right.hasRange

        override val hasUpdate: Boolean =
          left.hasUpdate || right.hasUpdate

        override val hasRemoveDeadline: Boolean =
          left.hasRemoveDeadline || right.hasRemoveDeadline

        def entriesCount: Int =
          _entries.size
      }

    def entries: ListBuffer[Point[K, V]] =
      left._entries.asInstanceOf[ListBuffer[Point[K, V]]]
  }

  sealed trait Batch[K, +V] extends MapEntry[K, V]
  sealed trait Point[K, +V] extends MapEntry[K, V] {
    def key: K

    def applyPoint[T >: V](skipList: SkipList[_, _, K, T]): Unit
  }

  case class Put[K, V](key: K,
                       value: V)(implicit serializer: MapEntryWriter[MapEntry.Put[K, V]]) extends MapEntry.Point[K, V] {

    private var calculatedEntriesByteSize: Int = -1
    def hasRange: Boolean = serializer.isRange
    def hasUpdate: Boolean = serializer.isUpdate
    def hasRemoveDeadline: Boolean =
      value match {
        case Memory.Remove(_, Some(_), _) => true
        case _ => false
      }

    /**
     * This function can be called multiple time. Store the calculation internally to save compute.
     */
    override def entryBytesSize: Int = {
      if (calculatedEntriesByteSize == -1)
        calculatedEntriesByteSize = serializer bytesRequired this

      calculatedEntriesByteSize
    }

    override def writeTo(slice: Slice[Byte]): Unit =
      serializer.write(this, slice)

    override def applyBatch[T >: V](skipList: SkipListBatchable[_, _, K, T]): Unit =
      skipList.put(key, value)

    override def applyPoint[T >: V](skipList: SkipList[_, _, K, T]): Unit =
      skipList.put(key, value)

    def entriesCount: Int =
      1

    //copy single creates a new Map entry clearing the ListBuffer. Immutable list is used here to speed boot-up
    //times when recovery key-values from Level0's map files. This should be changed to be immutable.
    def copySingle() =
      copy()

    override protected val _entries: ListBuffer[MapEntry.Point[K, _]] = ListBuffer(this)

  }

  case class Remove[K](key: K)(implicit serializer: MapEntryWriter[MapEntry.Remove[K]]) extends MapEntry.Point[K, Nothing] {

    private var calculatedEntriesByteSize: Int = -1
    def hasRange: Boolean = serializer.isRange
    def hasUpdate: Boolean = serializer.isUpdate
    def hasRemoveDeadline: Boolean = false

    /**
     * This function can be called multiple time. Store the calculation internally to save compute.
     */
    override def entryBytesSize: Int = {
      if (calculatedEntriesByteSize == -1)
        calculatedEntriesByteSize = serializer bytesRequired this

      calculatedEntriesByteSize
    }

    override def writeTo(slice: Slice[Byte]): Unit =
      serializer.write(this, slice)

    override def applyBatch[T >: Nothing](skipList: SkipListBatchable[_, _, K, T]): Unit =
      skipList.remove(key)

    override def applyPoint[T >: Nothing](skipList: SkipList[_, _, K, T]): Unit =
      skipList.remove(key)

    def entriesCount: Int =
      1

    def copySingle() =
      copy()

    override protected val _entries: ListBuffer[MapEntry.Point[K, _]] = ListBuffer(this)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy