diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt index adffdbf7..d7c60116 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/Envelope.kt @@ -28,7 +28,7 @@ interface Envelope { /** * Build a static envelope using provided builder */ - operator fun invoke(block: EnvelopeBuilder.() -> Unit) = EnvelopeBuilder().apply(block).build() + inline operator fun invoke(block: EnvelopeBuilder.() -> Unit) = EnvelopeBuilder().apply(block).build() } } diff --git a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt index 0f12c213..94f4e59d 100644 --- a/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt +++ b/dataforge-io/src/commonMain/kotlin/hep/dataforge/io/EnvelopeBuilder.kt @@ -18,8 +18,15 @@ class EnvelopeBuilder { metaBuilder.update(meta) } + /** + * The general purpose of the envelope + */ var type by metaBuilder.string(key = Envelope.ENVELOPE_TYPE_KEY) var dataType by metaBuilder.string(key = Envelope.ENVELOPE_DATA_TYPE_KEY) + + /** + * Data unique identifier to bypass identity checks + */ var dataID by metaBuilder.string(key = Envelope.ENVELOPE_DATA_ID_KEY) var description by metaBuilder.string(key = Envelope.ENVELOPE_DESCRIPTION_KEY) var name by metaBuilder.string(key = Envelope.ENVELOPE_NAME_KEY) @@ -32,5 +39,14 @@ class EnvelopeBuilder { data = ArrayBinary.write(builder = block) } - internal fun build() = SimpleEnvelope(metaBuilder.seal(), data) -} \ No newline at end of file + fun build() = SimpleEnvelope(metaBuilder.seal(), data) + +} + +//@ExperimentalContracts +//suspend fun EnvelopeBuilder.buildData(block: suspend Output.() -> Unit): Binary{ +// contract { +// callsInPlace(block, InvocationKind.EXACTLY_ONCE) +// } +// val scope = CoroutineScope(coroutineContext) +//} \ No newline at end of file diff --git a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt index 88819f2c..043283e3 100644 --- a/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt +++ b/dataforge-meta/src/commonMain/kotlin/hep/dataforge/meta/MutableMeta.kt @@ -115,6 +115,8 @@ operator fun MutableMeta<*>.set(name: NameToken, value: Any?) = set(name.asName( operator fun MutableMeta<*>.set(key: String, value: Any?) = set(key.toName(), value) +operator fun MutableMeta<*>.set(key: String, index: String, value: Any?) = set(key.toName().withIndex(index), value) + /** * Update existing mutable node with another node. The rules are following: * * value replaces anything @@ -161,7 +163,7 @@ operator fun MutableMeta<*>.set(name: String, metas: Iterable): Unit = set /** * Append the node with a same-name-sibling, automatically generating numerical index */ -fun > M.append(name: Name, value: Any?) { +fun > M.append(name: Name, value: Any?) { require(!name.isEmpty()) { "Name could not be empty for append operation" } val newIndex = name.last()!!.index if (newIndex.isNotEmpty()) { @@ -172,4 +174,4 @@ fun > M.append(name: Name, value: Any?) { } } -fun > M.append(name: String, value: Any?) = append(name.toName(), value) \ No newline at end of file +fun > M.append(name: String, value: Any?) = append(name.toName(), value) \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt new file mode 100644 index 00000000..2023d11b --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnHeader.kt @@ -0,0 +1,36 @@ +package hep.dataforge.tables + +import hep.dataforge.meta.Meta +import hep.dataforge.meta.get +import hep.dataforge.meta.int +import hep.dataforge.meta.string +import hep.dataforge.values.Value +import hep.dataforge.values.ValueType +import kotlin.reflect.KClass + +typealias TableHeader = List> + +typealias ValueTableHeader = List> + +interface ColumnHeader { + val name: String + val type: KClass + val meta: Meta +} + +data class SimpleColumnHeader( + override val name: String, + override val type: KClass, + override val meta: Meta +) : ColumnHeader + +val ColumnHeader.valueType: ValueType? get() = meta["valueType"].string?.let { ValueType.valueOf(it) } + +val ColumnHeader.textWidth: Int + get() = meta["columnWidth"].int ?: when (valueType) { + ValueType.NUMBER -> 8 + ValueType.STRING -> 16 + ValueType.BOOLEAN -> 5 + ValueType.NULL -> 5 + null -> 16 + } diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt index c48b30f8..dbb90cf2 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/ColumnTable.kt @@ -12,16 +12,20 @@ class ColumnTable(override val columns: Collection>) : Table< require(columns.all { it.size == rowsNum }) { "All columns must be of the same size" } } - override val rows: List + override val rows: List> get() = (0 until rowsNum).map { VirtualRow(this, it) } - override fun getValue(row: Int, column: String, type: KClass): T? { + override fun getValue(row: Int, column: String, type: KClass): T? { val value = columns[column]?.get(row) return type.cast(value) } } -internal class VirtualRow(val table: Table, val index: Int) : Row { - override fun getValue(column: String, type: KClass): T? = table.getValue(index, column, type) +internal class VirtualRow(val table: Table, val index: Int) : Row { + override fun getValue(column: String, type: KClass): T? = table.getValue(index, column, type) + +// override fun get(columnHeader: ColumnHeader): T? { +// return table.co[columnHeader][index] +// } } diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt index 421241d4..04c7d3a4 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MapRow.kt @@ -2,8 +2,8 @@ package hep.dataforge.tables import kotlin.reflect.KClass -inline class MapRow(val values: Map) : Row { - override fun getValue(column: String, type: KClass): T? { +inline class MapRow(val values: Map) : Row { + override fun getValue(column: String, type: KClass): T? { val value = values[column] return type.cast(value) } diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt index d7a360c9..8a9b06bc 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableColumnTable.kt @@ -10,11 +10,11 @@ class MutableColumnTable(val size: Int) : Table { private val _columns = ArrayList>() override val columns: List> get() = _columns - override val rows: List get() = (0 until size).map { + override val rows: List> get() = (0 until size).map { VirtualRow(this, it) } - override fun getValue(row: Int, column: String, type: KClass): T? { + override fun getValue(row: Int, column: String, type: KClass): T? { val value = columns[column]?.get(row) return type.cast(value) } diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt index 686e3c34..aa2d9abf 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/MutableTable.kt @@ -1,18 +1,14 @@ package hep.dataforge.tables import hep.dataforge.meta.Meta +import hep.dataforge.values.Value import kotlin.reflect.KClass -class SimpleColumnHeader( - override val name: String, - override val type: KClass, - override val meta: Meta -) : ColumnHeader - class MutableTable( - override val rows: MutableList, + override val rows: MutableList>, override val header: MutableList> -) : RowTable(rows, header) { +) : RowTable(rows, header) { + fun column(name: String, type: KClass, meta: Meta): ColumnHeader { val column = SimpleColumnHeader(name, type, meta) header.add(column) @@ -21,23 +17,24 @@ class MutableTable( inline fun column( name: String, - noinline columnMetaBuilder: ColumnScheme.() -> Unit + noinline columnMetaBuilder: ColumnScheme.() -> Unit = {} ): ColumnHeader { return column(name, T::class, ColumnScheme(columnMetaBuilder).toMeta()) } - fun row(block: MutableMap.() -> Unit): Row { - val map = HashMap().apply(block) + fun row(map: Map): Row { val row = MapRow(map) rows.add(row) return row } - operator fun MutableMap.set(header: ColumnHeader, value: T?) { - set(header.name, value) - } + fun row(vararg pairs: Pair, T>): Row = + row(pairs.associate { it.first.name to it.second }) } +fun MutableTable.row(vararg pairs: Pair, Any?>): Row = + row(pairs.associate { it.first.name to Value.of(it.second) }) + fun Table.edit(block: MutableTable.() -> Unit): Table { return MutableTable(rows.toMutableList(), header.toMutableList()).apply(block) } \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt index 98d7d4e1..c565d9cd 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/RowTable.kt @@ -13,12 +13,11 @@ internal class RowTableColumn(val table: Table, val header: C override fun get(index: Int): T? = table.rows[index].getValue(name, type) } -open class RowTable(override val rows: List, override val header: List>) : - Table { - override fun getValue(row: Int, column: String, type: KClass): T? = +open class RowTable(override val rows: List>, override val header: List>) : Table { + override fun getValue(row: Int, column: String, type: KClass): T? = rows[row].getValue(column, type) override val columns: List> get() = header.map { RowTableColumn(this, it) } } -suspend fun Rows.collect(): Table<*> = this as? Table<*> ?: RowTable(rowFlow().toList(), header) \ No newline at end of file +suspend fun Rows.collect(): Table = this as? Table ?: RowTable(rowFlow().toList(), header) \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt index 4e8215e0..71469984 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/Table.kt @@ -1,6 +1,5 @@ package hep.dataforge.tables -import hep.dataforge.meta.Meta import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import kotlin.reflect.KClass @@ -15,28 +14,30 @@ internal fun KClass.cast(value: Any?): T? { } } -typealias TableHeader = List> - /** * Finite or infinite row set. Rows are produced in a lazy suspendable [Flow]. * Each row must contain at least all the fields mentioned in [header]. */ -interface Rows { - val header: TableHeader<*> - fun rowFlow(): Flow +interface Rows { + val header: TableHeader + fun rowFlow(): Flow> } -interface Table : Rows { - fun getValue(row: Int, column: String, type: KClass): T? +interface Table : Rows { + fun getValue(row: Int, column: String, type: KClass): T? val columns: Collection> override val header: TableHeader get() = columns.toList() - val rows: List - override fun rowFlow(): Flow = rows.asFlow() + val rows: List> + override fun rowFlow(): Flow> = rows.asFlow() /** * Apply typed query to this table and return lazy [Flow] of resulting rows. The flow could be empty. */ //fun select(query: Any): Flow = error("Query of type ${query::class} is not supported by this table") + companion object { + inline operator fun invoke(block: MutableTable.() -> Unit): Table = + MutableTable(arrayListOf(), arrayListOf()).apply(block) + } } operator fun Collection>.get(name: String): Column<*>? = find { it.name == name } @@ -44,15 +45,9 @@ operator fun Collection>.get(name: String): Column<*>? = find { it.nam inline operator fun Table.get(row: Int, column: String): T? = getValue(row, column, T::class) -interface ColumnHeader { - val name: String - val type: KClass - val meta: Meta -} - operator fun Table.get(row: Int, column: Column): T? = getValue(row, column.name, column.type) -interface Column : ColumnHeader { +interface Column : ColumnHeader { val size: Int operator fun get(index: Int): T? } @@ -65,9 +60,9 @@ operator fun Column.iterator() = iterator { } } -interface Row { - fun getValue(column: String, type: KClass): T? +interface Row { + fun getValue(column: String, type: KClass): T? } -inline operator fun Row.get(column: String): T? = getValue(column, T::class) -operator fun Row.get(column: ColumnHeader): T? = getValue(column.name, column.type) \ No newline at end of file +inline operator fun Row.get(column: String): T? = getValue(column, T::class) +operator fun Row.get(column: ColumnHeader): T? = getValue(column.name, column.type) \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt index f4e73702..2675b483 100644 --- a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/TextRows.kt @@ -1,38 +1,56 @@ package hep.dataforge.tables.io -import hep.dataforge.meta.get -import hep.dataforge.meta.int -import hep.dataforge.meta.string import hep.dataforge.tables.* import hep.dataforge.values.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.toList import kotlinx.io.Binary import kotlinx.io.ExperimentalIoApi import kotlinx.io.Output import kotlinx.io.RandomAccessBinary import kotlinx.io.text.forEachUtf8Line import kotlinx.io.text.readUtf8Line +import kotlinx.io.text.readUtf8StringUntilDelimiter import kotlinx.io.text.writeUtf8String import kotlin.reflect.KClass -private fun readLine(header: List>, line: String): Row { - val values = line.split("\\s+".toRegex()).map { it.parseValue() } +/** + * Read a lin as a fixed width [Row] + */ +private fun readLine(header: ValueTableHeader, line: String): Row { + val values = line.trim().split("\\s+".toRegex()).map { it.lazyParseValue() } if (values.size == header.size) { val map = header.map { it.name }.zip(values).toMap() return MapRow(map) } else { - error("Can't read line $line. Expected ${header.size} values in a line, but found ${values.size}") + error("Can't read line \"$line\". Expected ${header.size} values in a line, but found ${values.size}") } } - +/** + * Finite or infinite [Rows] created from a fixed width text binary + */ @ExperimentalIoApi -class TextRows(override val header: List>, val binary: Binary) : Rows { +class TextRows(override val header: ValueTableHeader, val binary: Binary) : Rows { - override fun rowFlow(): Flow = binary.read { + /** + * A flow of indexes of string start offsets ignoring empty strings + */ + fun indexFlow(): Flow = binary.read { + var counter: Int = 0 + flow { + val string = readUtf8StringUntilDelimiter('\n') + counter += string.length + if (!string.isBlank()) { + emit(counter) + } + } + } + + override fun rowFlow(): Flow> = binary.read { flow { forEachUtf8Line { line -> if (line.isNotBlank()) { @@ -42,35 +60,57 @@ class TextRows(override val header: List>, val binary: Binar } } } + + companion object } +/** + * Create a row offset index for [TextRows] + */ +@ExperimentalIoApi +suspend fun TextRows.buildRowIndex(): List = indexFlow().toList() + +/** + * Finite table created from [RandomAccessBinary] with fixed width text table + */ @ExperimentalIoApi class TextTable( - override val header: List>, + override val header: ValueTableHeader, val binary: RandomAccessBinary, val index: List ) : Table { override val columns: Collection> get() = header.map { RowTableColumn(this, it) } - override val rows: List get() = index.map { readAt(it) } + override val rows: List> get() = index.map { readAt(it) } - override fun rowFlow(): Flow = TextRows(header, binary).rowFlow() + override fun rowFlow(): Flow> = TextRows(header, binary).rowFlow() - private fun readAt(offset: Int): Row { + private fun readAt(offset: Int): Row { return binary.read(offset) { val line = readUtf8Line() return@read readLine(header, line) } } - override fun getValue(row: Int, column: String, type: KClass): T? { + override fun getValue(row: Int, column: String, type: KClass): T? { val offset = index[row] return type.cast(readAt(offset)[column]) } + + companion object { + suspend operator fun invoke(header: ValueTableHeader, binary: RandomAccessBinary): TextTable { + val index = TextRows(header, binary).buildRowIndex() + return TextTable(header, binary, index) + } + } } -fun Output.writeValue(value: Value, width: Int, left: Boolean = true) { + +/** + * Write a fixed width value to the output + */ +private fun Output.writeValue(value: Value, width: Int, left: Boolean = true) { require(width > 5) { "Width could not be less than 5" } val str: String = when (value.type) { ValueType.NUMBER -> value.number.toString() //TODO apply decimal format @@ -90,31 +130,20 @@ fun Output.writeValue(value: Value, width: Int, left: Boolean = true) { writeUtf8String(padded) } -val ColumnHeader.valueType: ValueType? get() = meta["valueType"].string?.let { ValueType.valueOf(it) } - -private val ColumnHeader.width: Int - get() = meta["columnWidth"].int ?: when (valueType) { - ValueType.NUMBER -> 8 - ValueType.STRING -> 16 - ValueType.BOOLEAN -> 5 - ValueType.NULL -> 5 - null -> 16 - } - - /** * Write rows without header to the output */ -suspend fun Output.writeRows(rows: Rows) { +suspend fun Output.writeRows(rows: Rows) { @Suppress("UNCHECKED_CAST") val header = rows.header.map { if (it.type != Value::class) error("Expected Value column, but found ${it.type}") else (it as ColumnHeader) } val widths: List = header.map { - it.width + it.textWidth } rows.rowFlow().collect { row -> header.forEachIndexed { index, columnHeader -> writeValue(row[columnHeader] ?: Null, widths[index]) } + writeUtf8String("\r\n") } } \ No newline at end of file diff --git a/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt new file mode 100644 index 00000000..29ce27b2 --- /dev/null +++ b/dataforge-tables/src/commonMain/kotlin/hep/dataforge/tables/io/textTableEnvelope.kt @@ -0,0 +1,42 @@ +package hep.dataforge.tables.io + +import hep.dataforge.io.Envelope +import hep.dataforge.meta.* +import hep.dataforge.tables.SimpleColumnHeader +import hep.dataforge.tables.Table +import hep.dataforge.values.Value +import kotlinx.io.ByteArrayOutput +import kotlinx.io.EmptyBinary +import kotlinx.io.ExperimentalIoApi +import kotlinx.io.asBinary + + +@ExperimentalIoApi +suspend fun Table.wrap(): Envelope = Envelope { + meta { + header.forEachIndexed { index, columnHeader -> + set("column", index.toString(), buildMeta { + "name" put columnHeader.name + if (!columnHeader.meta.isEmpty()) { + "meta" put columnHeader.meta + } + }) + } + } + + type = "table.value" + dataID = "valueTable[${this@wrap.hashCode()}]" + + data = ByteArrayOutput().apply { writeRows(this@wrap) }.toByteArray().asBinary() +} + +@DFExperimental +@ExperimentalIoApi +fun TextRows.Companion.readEnvelope(envelope: Envelope): TextRows { + val header = envelope.meta.getIndexed("column") + .entries.sortedBy { it.key.toInt() } + .map { (_, item) -> + SimpleColumnHeader(item.node["name"].string!!, Value::class, item.node["meta"].node ?: Meta.EMPTY) + } + return TextRows(header, envelope.data ?: EmptyBinary) +} \ No newline at end of file diff --git a/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt b/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt deleted file mode 100644 index 010f903f..00000000 --- a/dataforge-tables/src/commonTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt +++ /dev/null @@ -1,6 +0,0 @@ -package hep.dataforge.tables.io - - -class TextRowsTest{ - -} \ No newline at end of file diff --git a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt index 1ac7e138..a0bcb75e 100644 --- a/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt +++ b/dataforge-tables/src/jvmMain/kotlin/hep/dataforge/tables/CastColumn.kt @@ -8,7 +8,7 @@ import kotlin.reflect.full.cast import kotlin.reflect.full.isSubclassOf @Suppress("UNCHECKED_CAST") -fun Column<*>.cast(type: KClass): Column { +fun Column<*>.cast(type: KClass): Column { return if (type.isSubclassOf(this.type)) { this as Column } else { @@ -16,7 +16,7 @@ fun Column<*>.cast(type: KClass): Column { } } -class CastColumn(val origin: Column<*>, override val type: KClass) : Column { +class CastColumn(val origin: Column<*>, override val type: KClass) : Column { override val name: String get() = origin.name override val meta: Meta get() = origin.meta override val size: Int get() = origin.size @@ -32,5 +32,5 @@ class ColumnProperty(val table: Table, val type: KClass) : } } -operator fun Collection>.get(header: ColumnHeader): Column? = +operator fun Collection>.get(header: ColumnHeader): Column? = find { it.name == header.name }?.cast(header.type) diff --git a/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt b/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt new file mode 100644 index 00000000..02d97caf --- /dev/null +++ b/dataforge-tables/src/jvmTest/kotlin/hep/dataforge/tables/io/TextRowsTest.kt @@ -0,0 +1,39 @@ +package hep.dataforge.tables.io + +import hep.dataforge.meta.DFExperimental +import hep.dataforge.tables.Table +import hep.dataforge.tables.get +import hep.dataforge.tables.row +import hep.dataforge.values.Value +import hep.dataforge.values.int +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.runBlocking +import kotlinx.io.ExperimentalIoApi +import kotlinx.io.toByteArray +import kotlin.test.Test +import kotlin.test.assertEquals + + +@DFExperimental +@ExperimentalIoApi +class TextRowsTest { + val table = Table { + val a = column("a") + val b = column("b") + row(a to 1, b to "b1") + row(a to 2, b to "b2") + } + + @Test + fun testTableWriteRead() { + runBlocking { + val envelope = table.wrap() + val string = envelope.data!!.toByteArray().decodeToString() + println(string) + val table = TextRows.readEnvelope(envelope) + val rows = table.rowFlow().toList() + assertEquals(1, rows[0]["a"]?.int) + assertEquals("b2", rows[1]["b"]?.string) + } + } +} \ No newline at end of file