Skip to content

Commit

Permalink
SQL #32: JDBC and SQLite templates, blocking eager fetching
Browse files Browse the repository at this point in the history
  • Loading branch information
Miha-x64 committed Mar 12, 2020
1 parent 4dcd78f commit c3113f4
Show file tree
Hide file tree
Showing 18 changed files with 686 additions and 249 deletions.
Expand Up @@ -8,8 +8,10 @@ import net.aquadc.persistence.sql.QueryBuilderTests
import net.aquadc.persistence.sql.Session
import net.aquadc.persistence.sql.SqlPropTest
import net.aquadc.persistence.sql.blocking.SqliteSession
import net.aquadc.persistence.sql.TemplatesTest
import net.aquadc.persistence.sql.TestTables
import net.aquadc.persistence.sql.dialect.sqlite.SqliteDialect
import net.aquadc.persistence.sql.blocking.Blocking
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
Expand Down Expand Up @@ -67,7 +69,6 @@ class SqlPropRoboTest : SqlPropTest() {

}


@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class EmbedRelationsRoboTest : EmbedRelationsTest() {
Expand All @@ -82,7 +83,6 @@ class EmbedRelationsRoboTest : EmbedRelationsTest() {
}
}


@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class QueryBuilderRoboTests : QueryBuilderTests() {
Expand All @@ -96,3 +96,17 @@ class QueryBuilderRoboTests : QueryBuilderTests() {
db.close()
}
}

@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class TemplatesRoboTests : TemplatesTest() {
private lateinit var db: SQLiteDatabase
override lateinit var session: Session<out Blocking<out AutoCloseable>>
@Before fun init() {
db = sqliteDb()
session = SqliteSession(db)
}
@After fun close() {
db.close()
}
}
48 changes: 48 additions & 0 deletions persistence/src/main/kotlin/net/aquadc/persistence/VarFuncImpl.kt
@@ -0,0 +1,48 @@
package net.aquadc.persistence

import androidx.annotation.RestrictTo

// TODO: looks like I should create a separate utility library for such things
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
abstract class VarFuncImpl<T, R> :
() -> R
, (T) -> R
, (T, T) -> R
, (T, T, T) -> R
, (T, T, T, T) -> R
, (T, T, T, T, T) -> R
, (T, T, T, T, T, T) -> R
, (T, T, T, T, T, T, T) -> R
, (T, T, T, T, T, T, T, T) -> R
{

abstract fun invokeUnchecked(vararg arg: T): R

override fun invoke(): R =
invokeUnchecked()

override fun invoke(p1: T): R =
invokeUnchecked(p1)

override fun invoke(p1: T, p2: T): R =
invokeUnchecked(p1, p2)

override fun invoke(p1: T, p2: T, p3: T): R =
invokeUnchecked(p1, p2, p3)

override fun invoke(p1: T, p2: T, p3: T, p4: T): R =
invokeUnchecked(p1, p2, p3, p4)

override fun invoke(p1: T, p2: T, p3: T, p4: T, p5: T): R =
invokeUnchecked(p1, p2, p3, p4, p5)

override fun invoke(p1: T, p2: T, p3: T, p4: T, p5: T, p6: T): R =
invokeUnchecked(p1, p2, p3, p4, p5, p6)

override fun invoke(p1: T, p2: T, p3: T, p4: T, p5: T, p6: T, p7: T): R =
invokeUnchecked(p1, p2, p3, p4, p5, p6, p7)

override fun invoke(p1: T, p2: T, p3: T, p4: T, p5: T, p6: T, p7: T, p8: T): R =
invokeUnchecked(p1, p2, p3, p4, p5, p6, p7, p8)

}
6 changes: 1 addition & 5 deletions sql/src/main/kotlin/net/aquadc/persistence/sql/RealDao.kt
Expand Up @@ -23,15 +23,11 @@ import java.util.concurrent.CopyOnWriteArraySet

internal class RealDao<SCH : Schema<SCH>, ID : IdBound, REC : Record<SCH, ID>, STMT>(
private val session: Session<*>,
private val lowSession: LowLevelSession<STMT>,
private val lowSession: LowLevelSession<STMT, *>,
private val table: Table<SCH, ID, REC>,
private val dialect: Dialect
) : Dao<SCH, ID, REC> {

// helpers for Sessions

internal val selectStatements = ThreadLocal<MutableMap<String, STMT>>()

// these three are guarded by RW lock
internal var insertStatement: STMT? = null
internal val updateStatements = New.map<Any, STMT>()
Expand Down
Expand Up @@ -15,7 +15,7 @@ import java.util.BitSet
)
internal class RealTransaction(
private val session: Session<*>,
private val lowSession: LowLevelSession<*>
private val lowSession: LowLevelSession<*, *>
) : Transaction {

private var thread: Thread? = Thread.currentThread() // null means that this transaction has ended
Expand Down
2 changes: 1 addition & 1 deletion sql/src/main/kotlin/net/aquadc/persistence/sql/Table.kt
Expand Up @@ -133,7 +133,7 @@ private constructor(
ss.colCount = outColumns.size - start
outRecipe.add(Nesting.StructEnd)

check(outDelegates?.put(path, Embedded<SCH, ID, Schema<*>>(
check(outDelegates?.put(path, Embedded(
outColumns.subList(start, outColumns.size).array(),
// ArrayList$SubList checks for concurrent modifications and cannot be passed as is
outRecipe.subList(recipeStart, outRecipe.size).array()
Expand Down
23 changes: 0 additions & 23 deletions sql/src/main/kotlin/net/aquadc/persistence/sql/async/collection.kt

This file was deleted.

211 changes: 211 additions & 0 deletions sql/src/main/kotlin/net/aquadc/persistence/sql/blocking/Blocking.kt
@@ -0,0 +1,211 @@
@file:Suppress("NOTHING_TO_INLINE")
package net.aquadc.persistence.sql.blocking

import net.aquadc.persistence.sql.BindBy
import net.aquadc.persistence.sql.Fetch
import net.aquadc.persistence.sql.Table
import net.aquadc.persistence.sql.inflate
import net.aquadc.persistence.sql.row
import net.aquadc.persistence.struct.Schema
import net.aquadc.persistence.struct.StoredNamedLens
import net.aquadc.persistence.struct.StructSnapshot
import net.aquadc.persistence.type.DataType
import net.aquadc.persistence.type.SimpleNullable
import java.io.InputStream
import java.sql.ResultSet
import java.sql.SQLFeatureNotSupportedException

/**
* SQL session tied to blocking API with cursors of type [CUR].
*/
interface Blocking<CUR : AutoCloseable> {
// Android SQLite API has special methods for single-cell selections
fun <T> cell(query: String, argumentTypes: Array<out DataType.Simple<*>>, arguments: Array<out Any>, type: DataType<T>): T

fun select(query: String, argumentTypes: Array<out DataType.Simple<*>>, arguments: Array<out Any>, expectedCols: Int): CUR

fun sizeHint(cursor: CUR): Int
fun next(cursor: CUR): Boolean

fun <T> cellAt(cursor: CUR, col: Int, type: DataType<T>): T
fun rowByName(cursor: CUR, columns: Array<out StoredNamedLens<*, *, *>>): Array<Any?>
fun rowByPosition(cursor: CUR, columns: Array<out StoredNamedLens<*, *, *>>): Array<Any?>
}

object Eager {
inline fun <CUR : AutoCloseable, R> cell(returnType: DataType.Simple<R>): Fetch<Blocking<CUR>, R> =
FetchCellEagerly(returnType)

inline fun <CUR : AutoCloseable, R : Any> cell(returnType: SimpleNullable<R>): Fetch<Blocking<CUR>, R?> =
FetchCellEagerly(returnType)

inline fun <CUR : AutoCloseable, R> col(elementType: DataType.Simple<R>): Fetch<Blocking<CUR>, List<R>> =
FetchColEagerly(elementType)

inline fun <CUR : AutoCloseable, R : Any> col(elementType: SimpleNullable<R>): Fetch<Blocking<CUR>, List<R?>> =
FetchColEagerly(elementType)

inline fun <CUR : AutoCloseable, SCH : Schema<SCH>> struct(table: Table<SCH, *, *>, bindBy: BindBy): Fetch<Blocking<CUR>, StructSnapshot<SCH>> =
FetchStructEagerly(table, bindBy)

inline fun <CUR : AutoCloseable, SCH : Schema<SCH>> structList(table: Table<SCH, *, *>, bindBy: BindBy): Fetch<Blocking<CUR>, List<StructSnapshot<SCH>>> =
FetchStructListEagerly(table, bindBy)

inline fun cellByteStream(): Fetch<Blocking<ResultSet>, InputStream> =
InputStreamFromResultSet // ^^^^^^^^^ JDBC-only. Not supported by Android SQLite
}

@PublishedApi internal class FetchCellEagerly<CUR : AutoCloseable, R>(
private val rt: DataType<R>
) : Fetch<Blocking<CUR>, R> {

override fun fetch(
from: Blocking<CUR>, query: String, argumentTypes: Array<out DataType.Simple<*>>, arguments: Array<out Any>
): R =
from.cell(query, argumentTypes, arguments, rt)
}

@PublishedApi internal class FetchColEagerly<CUR : AutoCloseable, R>(
private val et: DataType<R>
) : Fetch<Blocking<CUR>, List<R>> {

override fun fetch(
from: Blocking<CUR>, query: String, argumentTypes: Array<out DataType.Simple<*>>, arguments: Array<out Any>
): List<R> {
val cur = from.select(query, argumentTypes, arguments, 1)
try {
return if (from.next(cur)) {
val first = from.cellAt(cur, 0, et)
if (from.next(cur)) {
ArrayList<R>(from.sizeHint(cur).let { if (it < 0) 10 else it }).also {
it.add(first)
do it.add(from.cellAt(cur, 0, et)) while (from.next(cur))
}
} else listOf(first)
} else emptyList()
} finally {
cur.close()
}
}
}

@PublishedApi internal class FetchStructEagerly<SCH : Schema<SCH>, CUR : AutoCloseable>(
private val table: Table<SCH, *, *>,
private val bindBy: BindBy
) : Fetch<Blocking<CUR>, StructSnapshot<SCH>> {

override fun fetch(
from: Blocking<CUR>, query: String, argumentTypes: Array<out DataType.Simple<*>>, arguments: Array<out Any>
): StructSnapshot<SCH> {
val cur = from.select(query, argumentTypes, arguments, table.columns.size)
try {
check(from.next(cur))
val values = from.row(cur, table.columnsMappedToFields, bindBy)
check(!from.next(cur)) // single row expected
inflate(table.recipe, values, 0, 0, 0)
@Suppress("UNCHECKED_CAST")
return values[0] as StructSnapshot<SCH>
} finally {
cur.close()
}
}
}

@PublishedApi internal class FetchStructListEagerly<CUR : AutoCloseable, SCH : Schema<SCH>>(
private val table: Table<SCH, *, *>,
private val bindBy: BindBy
) : Fetch<Blocking<CUR>, List<StructSnapshot<SCH>>> {

override fun fetch(
from: Blocking<CUR>, query: String, argumentTypes: Array<out DataType.Simple<*>>, arguments: Array<out Any>
): List<StructSnapshot<SCH>> {
val cols = table.columnsMappedToFields
val recipe = table.recipe

val cur = from.select(query, argumentTypes, arguments, cols.size)
try {
return if (from.next(cur)) {
val first = from.mapRow(cur, cols, recipe)
if (from.next(cur)) {
ArrayList<StructSnapshot<SCH>>(from.sizeHint(cur).let { if (it < 0) 10 else it }).also {
it.add(first)
do it.add(from.mapRow(cur, cols, recipe)) while (from.next(cur))
}
} else listOf(first)
} else emptyList()
} finally {
cur.close()
}
}

private fun Blocking<CUR>.mapRow(cur: CUR, cols: Array<out StoredNamedLens<SCH, *, *>>, recipe: Array<out Table.Nesting>): StructSnapshot<SCH> {
val firstValues = row(cur, cols, bindBy); inflate(recipe, firstValues, 0, 0, 0)
@Suppress("UNCHECKED_CAST")
return firstValues[0] as StructSnapshot<SCH>
}
}

@PublishedApi internal object InputStreamFromResultSet : Fetch<Blocking<ResultSet>, InputStream> {

override fun fetch(
from: Blocking<ResultSet>, query: String, argumentTypes: Array<out DataType.Simple<*>>, arguments: Array<out Any>
): InputStream =
from.select(query, argumentTypes, arguments, 1).let {
check(it.next())
try {
it.getBlob(0).binaryStream // Postgres-JDBC supports this, SQLite-JDBC doesn't
} catch (e: SQLFeatureNotSupportedException) {
it.getBinaryStream(0) // this is typically just in-memory :'(
}
}
}

//fun <T> lazyValue(): FetchValue<BlockingSession, T, T, LazyList<T>> = TODO()
//fun <SCH : Schema<SCH>, D> lazyStruct(): FetchStruct<BlockingSession, SCH, Nothing, D, PropertyStruct<S>, LazyList<PropertyStruct<S>>> = TODO()

//fun <T> observableValue(/*todo dependencies*/): FetchValue<BlockingSession, T, Property<T>, Property<LazyList<T>>> = TODO()
//fun <SCH : Schema<SCH>, D, ID : IdBound> observableStruct(idName: String, idType: DataType.Simple<ID>/*todo dependencies*/): FetchStruct<BlockingSession, SCH, ID, D, PropertyStruct<S>, DiffProperty<LazyList<PropertyStruct<S>>, D>> = TODO()

/*fun <T> CoroutineScope.asyncValue(): FetchValue<BlockingSession, T, Deferred<T>, Deferred<List<T>>> {
launch { }
}
fun <SCH : Schema<SCH>, D> CoroutineScope.asyncStruct(): FetchStruct<BlockingSession, SCH, Nothing, D, Deferred<Struct<S>>, Deferred<List<Struct<S>>>> = TODO()*/

/*fun <T> cellCallback(cb: (T) -> Unit): FetchValue<BlockingSession, T, Unit, Nothing> = TODO()
fun <T> colCallback(cb: (List<T>) -> Unit): FetchValue<BlockingSession, T, Nothing, Unit> = TODO()
fun <SCH : Schema<SCH>, D> rowCallback(cb: (Struct<S>) -> Unit): FetchStruct<BlockingSession, SCH, Nothing, D, Unit, Nothing> = TODO()
fun <SCH : Schema<SCH>, D> gridCallback(cb: (List<Struct<S>>) -> Unit): FetchStruct<BlockingSession, SCH, Nothing, D, Nothing, Unit> = TODO()*/

/*
fun <T> CoroutineScope.lazyAsyncValue(): FetchValue<BlockingSession, T, Deferred<T>, AsyncList<T>> = TODO()
fun <SCH : Schema<SCH>, D> CoroutineScope.lazyAsyncStruct(): FetchStruct<BlockingSession, SCH, Nothing, D, AsyncStruct<S>, AsyncList<AsyncStruct<S>>> = TODO()
class ListChanges<SCH : Schema<SCH>, ID : IdBound>(
val oldIds: List<ID>, // List could wrap IntArray, for example. Array can't
val newIds: List<ID>,
val changes: Map<ID, FldSet<SCH>>
)
interface AsyncStruct<SCH : Schema<SCH>>
interface AsyncIterator<out T> {
suspend operator fun next(): T
suspend operator fun hasNext(): Boolean
}
interface AsyncIterable<out T> {
operator fun iterator(): AsyncIterator<T>
}
interface AsyncCollection<out E> : Iterable<E> {
suspend /*val*/ fun size(): Int
suspend /*operator*/ fun contains(element: @UnsafeVariance E): Boolean
suspend fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
}
interface AsyncList<out E> {
suspend /*operator*/ fun get(index: Int): E
suspend fun indexOf(element: @UnsafeVariance E): Int
suspend fun lastIndexOf(element: @UnsafeVariance E): Int
// fun listIterator(): ListIterator<E>
// fun listIterator(index: Int): ListIterator<E>
// suspend fun subList(fromIndex: Int, toIndex: Int): List<E>
}
*/

0 comments on commit c3113f4

Please sign in to comment.