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

swaydb.core.segment.SegmentAssigner.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.segment

import swaydb.core.data.{KeyValue, Memory, MemoryOption, Value}
import swaydb.core.util.DropIterator
import swaydb.core.util.skiplist.SkipList
import swaydb.data.order.KeyOrder
import swaydb.data.slice.{Slice, SliceOption}

import scala.annotation.tailrec
import scala.collection.mutable

private[core] object SegmentAssigner {

  def assignMinMaxOnlyUnsafe(inputSegments: Iterable[Segment],
                             targetSegments: Iterable[Segment])(implicit keyOrder: KeyOrder[Slice[Byte]]): Iterable[Segment] =
    SegmentAssigner.assignUnsafe(2 * inputSegments.size, Segment.tempMinMaxKeyValues(inputSegments), targetSegments).keys

  def assignMinMaxOnlyUnsafe(keyValues: Either[SkipList[SliceOption[Byte], MemoryOption, Slice[Byte], Memory], Slice[Memory]],
                             targetSegments: Iterable[Segment])(implicit keyOrder: KeyOrder[Slice[Byte]]): Iterable[Segment] =
    keyValues match {
      case Left(value) =>
        assignMinMaxOnlyUnsafe(value, targetSegments)

      case Right(value) =>
        assignMinMaxOnlyUnsafe(value, targetSegments)
    }

  def assignMinMaxOnlyUnsafe(input: SkipList[SliceOption[Byte], MemoryOption, Slice[Byte], Memory],
                             targetSegments: Iterable[Segment])(implicit keyOrder: KeyOrder[Slice[Byte]]): Iterable[Segment] =
    SegmentAssigner.assignUnsafe(2, Segment.tempMinMaxKeyValues(input), targetSegments).keys

  def assignMinMaxOnlyUnsafe(input: Slice[Memory],
                             targetSegments: Iterable[Segment])(implicit keyOrder: KeyOrder[Slice[Byte]]): Iterable[Segment] =
    SegmentAssigner.assignUnsafe(2, Segment.tempMinMaxKeyValues(input), targetSegments).keys

  def assignUnsafe(keyValues: Slice[KeyValue],
                   segments: Iterable[Segment])(implicit keyOrder: KeyOrder[Slice[Byte]]): mutable.Map[Segment, Slice[KeyValue]] =
    assignUnsafe(
      keyValuesCount = keyValues.size,
      keyValues = keyValues,
      segments = segments
    )

  /**
   * @param keyValuesCount keyValuesCount is needed here because keyValues could be a [[ConcurrentSkipList]]
   *                       where calculating size is not constant time.
   */
  def assignUnsafe(keyValuesCount: Int,
                   keyValues: Iterable[KeyValue],
                   segments: Iterable[Segment])(implicit keyOrder: KeyOrder[Slice[Byte]]): mutable.Map[Segment, Slice[KeyValue]] = {
    if (Segment hasOnlyOneSegment segments) { //.size iterates the entire Iterable which is not needed.
      mutable.Map((segments.head, Slice.from(keyValues, keyValuesCount)))
    } else {
      import keyOrder._

      val assignmentsMap = mutable.Map.empty[Segment, Slice[KeyValue]]
      val segmentsIterator = segments.iterator

      def getNextSegmentMayBe() = if (segmentsIterator.hasNext) segmentsIterator.next() else Segment.Null

      def assignKeyValueToSegment(segment: Segment,
                                  keyValue: KeyValue,
                                  remainingKeyValues: Int): Unit =
        assignmentsMap.get(segment) match {
          case Some(currentAssignments) =>
            currentAssignments add keyValue

          case None =>
            //+1 for cases when a Range might extend to the next Segment.
            val initial = Slice.of[KeyValue](remainingKeyValues + 1)
            initial add keyValue
            assignmentsMap += (segment -> initial)
        }

      @tailrec
      def assign(remainingKeyValues: DropIterator[Memory.Range, KeyValue],
                 thisSegmentMayBe: SegmentOption,
                 nextSegmentMayBe: SegmentOption): Unit =
        (remainingKeyValues.headOrNull, thisSegmentMayBe, nextSegmentMayBe) match {
          //add this key-value if it is the new smallest key or if this key belong to this Segment or if there is no next Segment
          case (keyValue: KeyValue, thisSegment: Segment, _) if keyValue.key <= thisSegment.minKey || Segment.belongsTo(keyValue, thisSegment) || nextSegmentMayBe.isNoneS =>
            keyValue match {
              case keyValue: KeyValue.Fixed =>
                assignKeyValueToSegment(thisSegment, keyValue, remainingKeyValues.size)
                assign(remainingKeyValues.dropHead(), thisSegmentMayBe, nextSegmentMayBe)

              case keyValue: KeyValue.Range =>
                nextSegmentMayBe match {
                  case nextSegment: Segment if keyValue.toKey > nextSegment.minKey =>
                    val (fromValue, rangeValue) = keyValue.fetchFromAndRangeValueUnsafe
                    val thisSegmentsRange = Memory.Range(fromKey = keyValue.fromKey, toKey = nextSegment.minKey, fromValue = fromValue, rangeValue = rangeValue)
                    val nextSegmentsRange = Memory.Range(fromKey = nextSegment.minKey, toKey = keyValue.toKey, fromValue = Value.FromValue.Null, rangeValue = rangeValue)

                    assignKeyValueToSegment(thisSegment, thisSegmentsRange, remainingKeyValues.size)
                    assign(remainingKeyValues.dropPrepend(nextSegmentsRange), nextSegment, getNextSegmentMayBe())

                  case _ =>
                    assignKeyValueToSegment(thisSegment, keyValue, remainingKeyValues.size)
                    assign(remainingKeyValues.dropHead(), thisSegmentMayBe, nextSegmentMayBe)
                }
            }


          // is this a gap key between thisSegment and the nextSegment
          case (keyValue: KeyValue, thisSegment: Segment, nextSegment: Segment) if keyValue.key < nextSegment.minKey =>
            keyValue match {
              case keyValue: KeyValue.Fixed =>
                //ignore if a key-value is not already assigned to thisSegment. No point adding a single key-value to a Segment.
                if (assignmentsMap.contains(thisSegment)) {
                  assignKeyValueToSegment(thisSegment, keyValue, remainingKeyValues.size)
                  assign(remainingKeyValues.dropHead(), thisSegmentMayBe, nextSegmentMayBe)
                } else {
                  assign(remainingKeyValues, nextSegment, getNextSegmentMayBe())
                }

              case keyValue: KeyValue.Range =>
                nextSegmentMayBe match {
                  case nextSegment: Segment if keyValue.toKey > nextSegment.minKey =>
                    //if it's a gap Range key-value and it's flows onto the next Segment, just jump to the next Segment.
                    assign(remainingKeyValues, nextSegment, getNextSegmentMayBe())

                  case _ =>
                    //ignore if a key-value is not already assigned to thisSegment. No point adding a single key-value to a Segment.
                    //same code as above, need to push it to a common function.
                    if (assignmentsMap.contains(thisSegment)) {
                      assignKeyValueToSegment(thisSegment, keyValue, remainingKeyValues.size)
                      assign(remainingKeyValues.dropHead(), thisSegmentMayBe, nextSegmentMayBe)
                    } else {
                      assign(remainingKeyValues, nextSegment, getNextSegmentMayBe())
                    }
                }
            }

          case (_: KeyValue, _: Segment, nextSegment: Segment) =>
            assign(remainingKeyValues, nextSegment, getNextSegmentMayBe())

          case (_, _, _) =>
            ()
        }

      if (segmentsIterator.hasNext) {
        assign(DropIterator(keyValuesCount, keyValues.iterator), segmentsIterator.next(), getNextSegmentMayBe())
        assignmentsMap map {
          case (segment, keyValues) =>
            (segment, keyValues.close())
        }
      } else {
        assignmentsMap
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy