diff --git a/dataforge-exposed/build.gradle.kts b/dataforge-exposed/build.gradle.kts new file mode 100644 index 00000000..66f826ec --- /dev/null +++ b/dataforge-exposed/build.gradle.kts @@ -0,0 +1,15 @@ +plugins { + id("ru.mipt.npm.gradle.jvm") +} + +dependencies { + api("org.jetbrains.exposed:exposed-core:0.31.1") + testImplementation("org.jetbrains.exposed:exposed-jdbc:0.31.1") + testImplementation("com.h2database:h2:1.4.200") + testImplementation("org.slf4j:slf4j-simple:1.7.30") + api(project(":dataforge-tables")) +} + +readme { + maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE +} diff --git a/dataforge-exposed/src/main/kotlin/space/kscience/dataforge/exposed/ExposedTable.kt b/dataforge-exposed/src/main/kotlin/space/kscience/dataforge/exposed/ExposedTable.kt new file mode 100644 index 00000000..2f79b604 --- /dev/null +++ b/dataforge-exposed/src/main/kotlin/space/kscience/dataforge/exposed/ExposedTable.kt @@ -0,0 +1,84 @@ +package space.kscience.dataforge.exposed + +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.transactions.transaction +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.tables.Column +import space.kscience.dataforge.tables.Row +import space.kscience.dataforge.tables.Table +import kotlin.reflect.KType +import kotlin.reflect.typeOf +import org.jetbrains.exposed.sql.Column as SqlColumn + +public class ExposedColumn( + public val db: Database, + public val sqlTable: IntIdTable, + public val sqlColumn: SqlColumn, + public override val type: KType, +) : Column { + public override val name: String + get() = sqlColumn.name + + public override val meta: Meta + get() = Meta.EMPTY + + public override val size: Int + get() = transaction(db) { sqlColumn.table.selectAll().count().toInt() } + + public override fun get(index: Int): T? = transaction(db) { + sqlTable.select { sqlTable.id eq index + 1 }.firstOrNull()?.getOrNull(sqlColumn) + } +} + +@Suppress("UNCHECKED_CAST") +public class ExposedRow( + public val db: Database, + public val sqlTable: IntIdTable, + public val sqlRow: ResultRow, +) : + Row { + public override fun get(column: String): T? = transaction(db) { + val theColumn = sqlTable.columns.find { it.name == column } as SqlColumn? ?: return@transaction null + sqlRow.getOrNull(theColumn) + } +} + +@Suppress("UNCHECKED_CAST") +public class ExposedTable(public val db: Database, public val sqlTable: IntIdTable, public val type: KType) : + Table { + public override val columns: List> = + sqlTable.columns.filterNot { it.name == "id" }.map { ExposedColumn(db, sqlTable, it as SqlColumn, type) } + + public override val rows: List> + get() = transaction(db) { + sqlTable.selectAll().map { ExposedRow(db, sqlTable, it) } + } + + public override operator fun get(row: Int, column: String): T? = transaction(db) { + val sqlColumn: SqlColumn = sqlTable.columns.find { it.name == column } as SqlColumn? + ?: return@transaction null + + sqlTable.select { sqlTable.id eq row + 1 }.firstOrNull()?.getOrNull(sqlColumn) + } +} + +public inline fun ExposedTable(db: Database, table: IntIdTable): ExposedTable = + ExposedTable(db, table, typeOf()) + +public inline fun ExposedTable( + db: Database, + tableName: String, + columns: List, + sqlColumnType: IColumnType, +): ExposedTable { + val table = object : IntIdTable(tableName) { + init { + columns.forEach { registerColumn(it, sqlColumnType) } + } + } + + transaction(db) { SchemaUtils.createMissingTablesAndColumns(table) } + return ExposedTable(db, table) +} diff --git a/dataforge-exposed/src/test/kotlin/space/kscience/dataforge/exposed/ExposedTableTest.kt b/dataforge-exposed/src/test/kotlin/space/kscience/dataforge/exposed/ExposedTableTest.kt new file mode 100644 index 00000000..e567101a --- /dev/null +++ b/dataforge-exposed/src/test/kotlin/space/kscience/dataforge/exposed/ExposedTableTest.kt @@ -0,0 +1,40 @@ +package space.kscience.dataforge.exposed + +import org.jetbrains.exposed.sql.Column +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.IntegerColumnType +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.transactions.transaction +import kotlin.test.Test +import kotlin.test.assertEquals + +@Suppress("UNCHECKED_CAST") +internal class ExposedTableTest { + @Test + fun exposedTable() { + val db = Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", driver = "org.h2.Driver") + + val table = ExposedTable( + db, + "test", + listOf("a", "b", "c"), + IntegerColumnType(), + ) + + transaction(db) { + table.sqlTable.insert { + it[table.sqlTable.columns.find { t -> t.name == "a" } as Column] = 42 + it[table.sqlTable.columns.find { t -> t.name == "b" } as Column] = 3 + it[table.sqlTable.columns.find { t -> t.name == "c" } as Column] = 7 + } + } + + + assertEquals(42, table[0, "a"]) + assertEquals(3, table[0, "b"]) + assertEquals(7, table[0, "c"]) + assertEquals(3, table.columns.size) + table.columns.forEach { assertEquals(1, it.size) } + assertEquals(1, table.rows.size) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index a5bf754f..c77e05e2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,5 +27,6 @@ include( // ":dataforge-output", ":dataforge-tables", ":dataforge-workspace", + ":dataforge-exposed", ":dataforge-scripting" ) \ No newline at end of file