feat: add catalog, table schema, and sorted index
This commit is contained in:
1 parent
8761ef2aa4
commit
22a32bfaf7
3 files changed
+151
No files matched your search
@@ -0,0 +1,100 @@
|
||||
package kestrel.catalog
|
||||
|
||||
import kestrel.KestrelException
|
||||
import kestrel.storage.Row
|
||||
import kestrel.storage.Table
|
||||
|
||||
class CatalogSnapshot(
|
||||
val tables: Map<String, Pair<TableSchema, List<Row>>>,
|
||||
val indexDefinitions: List<IndexDefinition>
|
||||
)
|
||||
|
||||
class Catalog {
|
||||
private val tables = linkedMapOf<String, Table>()
|
||||
private val indexes = linkedMapOf<String, SortedIndex>()
|
||||
|
||||
fun createTable(schema: TableSchema) {
|
||||
val key = schema.name.lowercase()
|
||||
if (tables.containsKey(key)) {
|
||||
throw KestrelException("table '${schema.name}' already exists")
|
||||
}
|
||||
tables[key] = Table(schema)
|
||||
}
|
||||
|
||||
fun dropTable(tableName: String) {
|
||||
val key = tableName.lowercase()
|
||||
if (tables.remove(key) == null) {
|
||||
throw KestrelException("no such table: $tableName")
|
||||
}
|
||||
indexes.entries.removeAll { it.value.definition.tableName.equals(tableName, ignoreCase = true) }
|
||||
}
|
||||
|
||||
fun table(tableName: String): Table =
|
||||
tables[tableName.lowercase()] ?: throw KestrelException("no such table: $tableName")
|
||||
|
||||
fun tableOrNull(tableName: String): Table? = tables[tableName.lowercase()]
|
||||
|
||||
val tableNames: List<String> get() = tables.values.map { it.schema.name }
|
||||
|
||||
fun createIndex(definition: IndexDefinition) {
|
||||
val key = definition.name.lowercase()
|
||||
if (indexes.containsKey(key)) {
|
||||
throw KestrelException("index '${definition.name}' already exists")
|
||||
}
|
||||
val table = table(definition.tableName)
|
||||
if (table.schema.columnIndex(definition.columnName) == null) {
|
||||
throw KestrelException("no such column: ${definition.columnName} in table ${definition.tableName}")
|
||||
}
|
||||
val index = SortedIndex(definition)
|
||||
indexes[key] = index
|
||||
reindexOne(table, index)
|
||||
}
|
||||
|
||||
fun dropIndex(indexName: String) {
|
||||
if (indexes.remove(indexName.lowercase()) == null) {
|
||||
throw KestrelException("no such index: $indexName")
|
||||
}
|
||||
}
|
||||
|
||||
fun indexOn(tableName: String, columnName: String): SortedIndex? = indexes.values.firstOrNull {
|
||||
it.definition.tableName.equals(tableName, ignoreCase = true) &&
|
||||
it.definition.columnName.equals(columnName, ignoreCase = true)
|
||||
}
|
||||
|
||||
val indexNames: List<String> get() = indexes.values.map { it.definition.name }
|
||||
|
||||
fun indexDefinitions(): List<IndexDefinition> = indexes.values.map { it.definition }
|
||||
|
||||
fun reindexTable(tableName: String) {
|
||||
val table = table(tableName)
|
||||
indexes.values
|
||||
.filter { it.definition.tableName.equals(tableName, ignoreCase = true) }
|
||||
.forEach { reindexOne(table, it) }
|
||||
}
|
||||
|
||||
fun snapshot(): CatalogSnapshot = CatalogSnapshot(
|
||||
tables = tables.mapValues { (_, table) -> table.schema to table.rows.toList() },
|
||||
indexDefinitions = indexes.values.map { it.definition }
|
||||
)
|
||||
|
||||
fun restore(snapshot: CatalogSnapshot) {
|
||||
tables.clear()
|
||||
indexes.clear()
|
||||
for ((key, image) in snapshot.tables) {
|
||||
val table = Table(image.first)
|
||||
table.rows.addAll(image.second)
|
||||
tables[key] = table
|
||||
}
|
||||
for (definition in snapshot.indexDefinitions) {
|
||||
val index = SortedIndex(definition)
|
||||
indexes[definition.name.lowercase()] = index
|
||||
reindexOne(tables.getValue(definition.tableName.lowercase()), index)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reindexOne(table: Table, index: SortedIndex) {
|
||||
index.clear()
|
||||
val columnIndex = table.schema.columnIndex(index.definition.columnName)!!
|
||||
table.rows.forEachIndexed { rowIndex, row -> index.add(row.values[columnIndex], rowIndex) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package kestrel.catalog
|
||||
|
||||
import java.util.NavigableMap
|
||||
import java.util.TreeMap
|
||||
import kestrel.storage.Value
|
||||
import kestrel.storage.ValueOrdering
|
||||
|
||||
data class IndexDefinition(val name: String, val tableName: String, val columnName: String)
|
||||
|
||||
class SortedIndex(val definition: IndexDefinition) {
|
||||
private val entries: TreeMap<Value, MutableList<Int>> = TreeMap(ValueOrdering)
|
||||
|
||||
fun clear() = entries.clear()
|
||||
|
||||
fun add(key: Value, rowIndex: Int) {
|
||||
if (key is Value.NullValue) return
|
||||
entries.getOrPut(key) { mutableListOf() }.add(rowIndex)
|
||||
}
|
||||
|
||||
fun equalTo(key: Value): List<Int> = entries[key]?.toList() ?: emptyList()
|
||||
|
||||
fun rangeOf(lower: Value?, lowerInclusive: Boolean, upper: Value?, upperInclusive: Boolean): List<Int> {
|
||||
var view: NavigableMap<Value, MutableList<Int>> = entries
|
||||
if (lower != null) view = view.tailMap(lower, lowerInclusive)
|
||||
if (upper != null) view = view.headMap(upper, upperInclusive)
|
||||
return view.values.flatten()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package kestrel.catalog
|
||||
|
||||
import kestrel.storage.ColumnType
|
||||
|
||||
data class Column(
|
||||
val name: String,
|
||||
val type: ColumnType,
|
||||
val notNull: Boolean,
|
||||
val primaryKey: Boolean
|
||||
)
|
||||
|
||||
class TableSchema(val name: String, val columns: List<Column>) {
|
||||
private val indexByName: Map<String, Int> =
|
||||
columns.withIndex().associate { (index, column) -> column.name.lowercase() to index }
|
||||
|
||||
val columnNames: List<String> get() = columns.map { it.name }
|
||||
|
||||
fun columnIndex(columnName: String): Int? = indexByName[columnName.lowercase()]
|
||||
|
||||
fun column(columnName: String): Column? = columnIndex(columnName)?.let { columns[it] }
|
||||
|
||||
val primaryKeyIndex: Int? get() = columns.indexOfFirst { it.primaryKey }.takeIf { it >= 0 }
|
||||
}
|
||||
Reference in new issue
Block a user