Skip to content

Commit

Permalink
Fixed FixedLengthColumn and VariableLengthColumn.
Browse files Browse the repository at this point in the history
  • Loading branch information
ppanopticon committed Nov 18, 2023
1 parent b2b9621 commit ad1f6d2
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 97 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@ const val BOC: TupleId = -1L
/** Type alias for [TupleId]; a [TupleId] is a positive [Long] value (negative [TupleId]s are invalid).*/
typealias TupleId = Long

/** Type alias for [TabletId]; a [TabletId] is a positive [Long] value (negative [TabletId]s are invalid).*/
typealias TabletId = Long

/** Type alias for [TransactionId]; a [TransactionId] is a positive [Long] value (negative [TransactionId]s are invalid).*/
typealias TransactionId = Long
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,12 @@ import org.vitrivr.cottontail.dbms.exceptions.DatabaseException
import org.vitrivr.cottontail.dbms.general.AbstractTx
import org.vitrivr.cottontail.dbms.queries.context.QueryContext
import org.vitrivr.cottontail.dbms.statistics.defaultStatistics
import org.vitrivr.cottontail.dbms.statistics.values.*
import org.vitrivr.cottontail.dbms.statistics.values.ValueStatistics
import org.vitrivr.cottontail.storage.serializers.SerializerFactory
import org.vitrivr.cottontail.storage.serializers.tablets.Compression
import org.vitrivr.cottontail.storage.serializers.tablets.TabletSerializer
import kotlin.concurrent.withLock

typealias TabletId = Long

/**
* The default [Column] implementation for fixed-length columns based on JetBrains Xodus. [Value]s are stored in [AbstractTablet]s of 64 entries.
*
Expand All @@ -37,7 +35,13 @@ typealias TabletId = Long
* @author Ralph Gasser
* @version 1.0.0
*/
class FixedLengthColumn<T : Value>(override val columnDef: ColumnDef<T>, override val parent: DefaultEntity, private val compression: Compression) : Column<T> {
class FixedLengthColumn<T : Value>(override val columnDef: ColumnDef<T>, override val parent: DefaultEntity, val compression: Compression) : Column<T> {

companion object {
const val TABLET_SIZE = 128
const val TABLET_SHR = 7
}


/** The [Name.ColumnName] of this [VariableLengthColumn]. */
override val name: Name.ColumnName
Expand All @@ -47,9 +51,6 @@ class FixedLengthColumn<T : Value>(override val columnDef: ColumnDef<T>, overrid
override val catalogue: DefaultCatalogue
get() = this.parent.catalogue

/** */
private val tabletSize: Int = 128

init {
require(type !is Types.String && this.columnDef.type !is Types.ByteString) {
"FixedLengthColumn can only be used for fixed-length types."
Expand Down Expand Up @@ -82,12 +83,11 @@ class FixedLengthColumn<T : Value>(override val columnDef: ColumnDef<T>, overrid
false
) ?: throw DatabaseException.DataCorruptionException("Data store for column ${this@FixedLengthColumn.name} is missing.")


/** The internal [TabletSerializer] reference used for de-/serialization. */
private val serializer: TabletSerializer<T> = SerializerFactory.tablet(this@FixedLengthColumn.columnDef.type, this@FixedLengthColumn.tabletSize, this@FixedLengthColumn.compression)
private val serializer: TabletSerializer<T> = SerializerFactory.tablet(this@FixedLengthColumn.columnDef.type, TABLET_SIZE, this@FixedLengthColumn.compression)

/** The [TabletId] of the currently loaded [Tablet]. -1 if no [Tablet] has been loaded. */
private var tabletId: TabletId = -1
/** The [TupleId] of the currently loaded [Tablet]. -1 if no [Tablet] has been loaded. */
private var tabletId: TupleId = -1

/** The currently loaded [Tablet]. */
private var tablet: Tablet<T>? = null
Expand Down Expand Up @@ -117,12 +117,10 @@ class FixedLengthColumn<T : Value>(override val columnDef: ColumnDef<T>, overrid
* @return The desired [Value] or null.
*/
override fun read(tupleId: TupleId): T? = this.txLatch.withLock {
val tabletId = tupleId ushr 6
val position = (tupleId % Long.SIZE_BITS).toInt()
if (this.tabletId != tabletId) {
loadTabletForTuple(tabletId)
}
return this.tablet!![position]
val tabletId = tupleId ushr TABLET_SHR
val tabletIdx = (tupleId % TABLET_SIZE).toInt()
loadTabletF(tabletId)
return this.tablet!![tabletIdx]
}

/**
Expand All @@ -133,13 +131,11 @@ class FixedLengthColumn<T : Value>(override val columnDef: ColumnDef<T>, overrid
* @return The desired entry.
*/
override fun write(tupleId: TupleId, value: T) = this.txLatch.withLock {
val tabletId = tupleId ushr 6
val position = (tupleId % Long.SIZE_BITS).toInt()
if (this.tabletId != tabletId) {
loadTabletForTuple(tabletId)
}
val old = this.tablet!![position]
this.tablet!![position] = value
val tabletId = tupleId ushr TABLET_SHR
val tabletIdx = (tupleId % TABLET_SIZE).toInt()
loadTabletF(tabletId)
val old = this.tablet!![tabletIdx]
this.tablet!![tabletIdx] = value
this.dirty = true
old
}
Expand All @@ -151,13 +147,11 @@ class FixedLengthColumn<T : Value>(override val columnDef: ColumnDef<T>, overrid
* @return The previous [Value] entry.
*/
override fun delete(tupleId: TupleId): T? = this.txLatch.withLock {
val tabletId = tupleId ushr 6
val position = (tupleId % tabletId).toInt()
if (this.tabletId != tabletId) {
loadTabletForTuple(tabletId)
}
val old = this.tablet!![position]
this.tablet!![position] = null
val tabletId = tupleId ushr TABLET_SHR
val tabletIdx = (tupleId % TABLET_SIZE).toInt()
loadTabletF(tabletId)
val old = this.tablet!![tabletIdx]
this.tablet!![tabletIdx] = null
this.dirty = true
old
}
Expand All @@ -169,37 +163,42 @@ class FixedLengthColumn<T : Value>(override val columnDef: ColumnDef<T>, overrid
*/
@Suppress("UNCHECKED_CAST")
override fun cursor(): Cursor<T> {
if (this.dataStore.count(this.context.txn.xodusTx) == 0L) return EmptyColumnCursor as Cursor<T>
return VariableLengthCursor(this@FixedLengthColumn, this.context.txn)
val count = this.dataStore.count(this.context.txn.xodusTx)
if (count == 0L) return EmptyColumnCursor as Cursor<T>
return FixedLengthCursor(this)
}

/**
* Flushes in-memory [AbstractTablet] to disk, if needed.
*/
override fun beforeCommit() {
val tablet = this.tablet
if (this.dirty && tablet != null) {
this.dataStore.put(this.context.txn.xodusTx, LongBinding.longToCompressedEntry(this.tabletId), this.serializer.toEntry(tablet))
this.dirty = false
}
this.flushTablet()
}

/**
* Loads the [AbstractTablet] with the specified [TabletId] into memory.
* Loads the [AbstractTablet] with the specified [TupleId] into memory.
*
* @param tabletId [TabletId] of the [AbstractTablet] to load.
* @param tabletId [TupleId] of the [AbstractTablet] to load.
*/
private fun loadTabletForTuple(tabletId: TabletId) {
if (this.dirty && this.tablet != null) { /* Flush current tablet if needed. */
this.dataStore.put(this.context.txn.xodusTx, LongBinding.longToCompressedEntry(this.tabletId), this.serializer.toEntry(this.tablet!!))
this.dirty = false
}
private fun loadTabletF(tabletId: TupleId) {
if (this.tabletId == tabletId) return
this.flushTablet() /* Flush tablet to disk if necessary. */
this.tabletId = tabletId
val rawTablet = this.dataStore.get(this.context.txn.xodusTx, LongBinding.longToCompressedEntry(tabletId))
if (rawTablet != null) {
this.tablet = this.serializer.fromEntry(rawTablet)
} else {
this.tablet = Tablet.of(this@FixedLengthColumn.tabletSize, this@FixedLengthColumn.type)
this.tablet = Tablet.of(TABLET_SIZE, this@FixedLengthColumn.type)
}
}

/**
* Flushes the currently active tablet to disk, if necessary.
*/
private fun flushTablet() {
if (this.dirty && this.tablet != null) { /* Flush current tablet if needed. */
this.dataStore.put(this.context.txn.xodusTx, LongBinding.longToCompressedEntry(this.tabletId), this.serializer.toEntry(this.tablet!!))
this.dirty = false
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,65 +1,103 @@
package org.vitrivr.cottontail.dbms.column

import jetbrains.exodus.bindings.LongBinding
import jetbrains.exodus.env.Store
import jetbrains.exodus.env.StoreConfig
import org.vitrivr.cottontail.core.basics.Cursor
import org.vitrivr.cottontail.core.database.TabletId
import org.vitrivr.cottontail.core.database.TupleId
import org.vitrivr.cottontail.core.types.Value
import org.vitrivr.cottontail.core.values.tablets.Tablet
import org.vitrivr.cottontail.dbms.catalogue.storeName
import org.vitrivr.cottontail.dbms.column.FixedLengthColumn.Companion.TABLET_SHR
import org.vitrivr.cottontail.dbms.column.FixedLengthColumn.Companion.TABLET_SIZE
import org.vitrivr.cottontail.dbms.exceptions.DatabaseException
import org.vitrivr.cottontail.dbms.execution.transactions.Transaction
import org.vitrivr.cottontail.dbms.execution.transactions.TransactionMetadata
import org.vitrivr.cottontail.storage.serializers.SerializerFactory
import org.vitrivr.cottontail.storage.serializers.tablets.TabletSerializer

/**
*
* A [Cursor] to iterate over the [Tablet]s of a [Column].
*/
class FixedLengthCursor<T: Value>(private val column: Column<T>, private val transaction: Transaction): Cursor<T> {
class FixedLengthCursor<T: Value>(column: FixedLengthColumn<T>.Tx): Cursor<T> {

/** The Xodus transaction snapshot used by this FixedLengthCursor. */
private val xodusTx = column.context.txn.xodusTx.readonlySnapshot

/** Internal data [Store] reference. */
private val dataStore: Store = this.column.catalogue.transactionManager.environment.openStore(
this.column.name.storeName(),
private val store: Store = this.xodusTx.environment.openStore(
column.dbo.name.storeName(),
StoreConfig.USE_EXISTING,
this.transaction.xodusTx,
xodusTx,
false
) ?: throw DatabaseException.DataCorruptionException("Data store for column ${this.column.name} is missing.")
) ?: throw DatabaseException.DataCorruptionException("Data store for column ${column.dbo.name} is missing.")


/** The internal [TabletSerializer] reference used for de-/serialization. */
private val serializer: TabletSerializer<T> = SerializerFactory.tablet(this.column.columnDef.type, 128)
private val serializer: TabletSerializer<T> = SerializerFactory.tablet(column.columnDef.type, TABLET_SIZE, column.dbo.compression)

/** Internal Xodus cursor instance. */
private val cursor = this.dataStore.openCursor(this.transaction.xodusTx)
private val cursor = this.store.openCursor(this.xodusTx)

/** The [TupleId] of the currently loaded [Tablet]. -1 if no [Tablet] has been loaded. */
private var tupleId: TupleId = -1L

/** The [TabletId] of the currently loaded [Tablet]. -1 if no [Tablet] has been loaded. */
private var tupleId: TabletId = -1
/** The [TupleId] of the currently loaded [Tablet]. -1 if no [Tablet] has been loaded. */
private var tabletId: TabletId= -1L

/** The [TabletId] of the currently loaded [Tablet]. -1 if no [Tablet] has been loaded. */
/** The [TupleId] of the currently loaded [Tablet]. -1 if no [Tablet] has been loaded. */
private var tabletIndex: Int = -1

/** The currently loaded [Tablet]. */
private var tablet: Tablet<T>? = null

override fun moveNext(): Boolean = moveTo(this.tupleId + 1)
override fun movePrevious(): Boolean = moveTo(this.tupleId - 1)
override fun moveTo(tupleId: TupleId): Boolean {
/* Reposition cursor. */
val tabletId = tupleId ushr TABLET_SHR

override fun moveNext(): Boolean {
do {
this.tupleId += this.tupleId + 1L
this.tabletIndex = ((++this.tupleId) % Long.SIZE_BITS).toInt()
if (this.tabletIndex == 0 && this.cursor.next) {
if (this.cursor.next) {
this.tablet = this.serializer.fromEntry(this.cursor.value)
return true
} else {
return false
}
} else if (this.tabletIndex > 0 && this.tablet!![this.tabletIndex] != null) {
return true
}
} while (true)
/* Load tablet. */
return if (this.loadTablet(tabletId)) {
this.tupleId = tupleId
this.tabletIndex = ((tupleId) % TABLET_SIZE).toInt()
true
} else {
false
}
}

override fun key(): TupleId = this.tupleId
override fun value(): T = this.tablet!![this.tabletIndex] ?: throw NoSuchElementException("")
override fun close() = this.cursor.close()

/**
* Loads the [Tablet] with the given [TabletId] into memory.
*
* @param tabletId The [TabletId] of the [Tablet] to load.
* @return True on success, false otherwise.
*/
private fun loadTablet(tabletId: TabletId): Boolean {
if (this.tabletId == tabletId) return true
val moved = when (tabletId) {
this.tabletId + 1 -> this.cursor.next
this.tabletId - 1 -> this.cursor.prev
else -> this.cursor.getSearchKey(LongBinding.longToCompressedEntry(tabletId)) != null
}

/* If cursor did not move, return false. */
if (!moved) return false

/* Load tablet and return serialized value. */
val tablet = this.cursor.value
this.tablet = this.serializer.fromEntry(tablet)
this.tabletId = tabletId
return true
}

/**
* Closes this [FixedLengthCursor].
*/
override fun close() {
this.cursor.close()
this.xodusTx.abort()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,10 @@ class VariableLengthColumn<T : Value>(override val columnDef: ColumnDef<T>, over
* @return [Cursor]
*/
@Suppress("UNCHECKED_CAST")
override fun cursor(): Cursor<T> {
if (this.dataStore.count(this.context.txn.xodusTx) == 0L) return EmptyColumnCursor as Cursor<T>
return VariableLengthCursor(this@VariableLengthColumn, this.context.txn)
override fun cursor(): Cursor<T> = this.txLatch.withLock {
val count = this.dataStore.count(this.context.txn.xodusTx)
if (count == 0L) return EmptyColumnCursor as Cursor<T>
return VariableLengthCursor(this)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,49 @@ import org.vitrivr.cottontail.core.database.TupleId
import org.vitrivr.cottontail.core.types.Value
import org.vitrivr.cottontail.dbms.catalogue.storeName
import org.vitrivr.cottontail.dbms.exceptions.DatabaseException
import org.vitrivr.cottontail.dbms.execution.transactions.Transaction
import org.vitrivr.cottontail.storage.serializers.SerializerFactory
import org.vitrivr.cottontail.storage.serializers.values.ValueSerializer

/**
*
*/
class VariableLengthCursor<T: Value>(private val column: Column<T>, private val transaction: Transaction): Cursor<T> {
class VariableLengthCursor<T: Value>(column: VariableLengthColumn<T>.Tx): Cursor<T> {
/** The Xodus transaction snapshot used by this FixedLengthCursor. */
private val xodusTx = column.context.txn.xodusTx.readonlySnapshot

/** Internal data [Store] reference. */
private val dataStore: Store = this.column.catalogue.transactionManager.environment.openStore(
this.column.name.storeName(),
private val store: Store = this.xodusTx.environment.openStore(
column.dbo.name.storeName(),
StoreConfig.USE_EXISTING,
this.transaction.xodusTx,
xodusTx,
false
) ?: throw DatabaseException.DataCorruptionException("Data store for column ${this.column.name} is missing.")
) ?: throw DatabaseException.DataCorruptionException("Data store for column ${column.dbo.name} is missing.")

/** The internal [ValueSerializer] reference used for de-/serialization. */
private val binding: ValueSerializer<T> = SerializerFactory.value(this.column.columnDef.type)

/** Internal Xodus transaction snapshot. */
private val snapshot = this.transaction.xodusTx.readonlySnapshot
private val binding: ValueSerializer<T> = SerializerFactory.value(column.columnDef.type)

/** Internal Xodus cursor instance. */
private val cursor = this.dataStore.openCursor(this.transaction.xodusTx)
override fun moveNext(): Boolean = this.cursor.next
override fun key(): TupleId = LongBinding.compressedEntryToLong(this.cursor.key)
private val cursor = this.store.openCursor(this.xodusTx)

/** The [TupleId] this [VariableLengthCursor] is currently pointing to. */
private var tupleId: TupleId = -1L

override fun moveNext(): Boolean = this.moveTo(this.tupleId + 1)
override fun movePrevious(): Boolean = this.moveTo(this.tupleId - 1)
override fun moveTo(tupleId: TupleId): Boolean {
val ret = when (tupleId) {
this.tupleId + 1 -> this.cursor.next
this.tupleId - 1 -> this.cursor.prev
else -> this.cursor.getSearchKey(LongBinding.longToCompressedEntry(tupleId)) != null
}
if (ret) this.tupleId = tupleId
return ret
}

override fun key(): TupleId = this.tupleId
override fun value(): T = this.binding.fromEntry(this.cursor.value)!!
override fun close() {
this.cursor.close()
this.snapshot.abort()
this.xodusTx.abort()
}
}
Loading

0 comments on commit ad1f6d2

Please sign in to comment.