Skip to content

Commit 9831b53

Browse files
committed
Save before changing binder
1 parent ddd3c6b commit 9831b53

File tree

23 files changed

+754
-74
lines changed

23 files changed

+754
-74
lines changed

SQLager/gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ kotlin.code.style=official
1919
GROUP=co.touchlab
2020
VERSION_NAME=0.1.0-SNAPSHOT
2121

22-
STATELY_VERSION=0.3.9-SNAPSHOT
22+
STATELY_VERSION=0.4.0-SNAPSHOT
2323
SQLITER_VERSION=0.4.3-SNAPSHOT
2424
KOTLIN_VERSION=1.3.10
2525

SQLager/runtest.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#Runs specific test. Pass in test name.
2+
./gradlew linkTestDebugExecutableMacos
3+
./build/bin/macos/test/debug/executable/test.kexe --ktest_regex_filter=.*$1.*
Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,26 @@
11
package co.touchlab.sqlager.user
22

33
interface Binder{
4-
54
fun bytes(
6-
bytes: ByteArray,
5+
bytes: ByteArray?,
76
index: Int = AUTO_INDEX,
87
name: String? = null
98
)
109

1110
fun double(
12-
double: Double,
11+
double: Double?,
1312
index: Int = AUTO_INDEX,
1413
name: String? = null
1514
)
1615

1716
fun long(
18-
long: Long,
17+
long: Long?,
1918
index: Int = AUTO_INDEX,
2019
name: String? = null
2120
)
2221

2322
fun string(
24-
string: String,
23+
string: String?,
2524
index: Int = AUTO_INDEX,
2625
name: String? = null
2726
)
@@ -32,4 +31,20 @@ interface Binder{
3231
)
3332
}
3433

34+
fun Binder.int(
35+
int: Int?,
36+
index: Int = AUTO_INDEX,
37+
name: String? = null
38+
){
39+
long(int?.toLong(), index, name)
40+
}
41+
42+
fun Binder.float(
43+
float: Float?,
44+
index: Int = AUTO_INDEX,
45+
name: String? = null
46+
){
47+
double(float?.toDouble(), index, name)
48+
}
49+
3550
internal val AUTO_INDEX = -1

SQLager/src/commonMain/kotlin/co/touchlab/sqlager/user/BinderStatement.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
11
package co.touchlab.sqlager.user
22

3-
import co.touchlab.sqliter.Statement
3+
import co.touchlab.sqliter.*
44
import co.touchlab.stately.concurrency.AtomicInt
55

66
class BinderStatement internal constructor(internal val sql:String, internal val statement: Statement):
77
Binder {
88

99
private val indexCounter = AtomicInt(0)
1010

11-
override fun bytes(bytes: ByteArray, index: Int, name: String?) {
11+
override fun bytes(bytes: ByteArray?, index: Int, name: String?) {
1212
statement.bindBlob(bindIndex(index, name), bytes)
1313
}
1414

15-
override fun double(double: Double, index: Int, name: String?) {
15+
override fun double(double: Double?, index: Int, name: String?) {
1616
statement.bindDouble(bindIndex(index, name), double)
1717
}
1818

19-
override fun long(long: Long, index: Int, name: String?) {
19+
override fun long(long: Long?, index: Int, name: String?) {
2020
statement.bindLong(bindIndex(index, name), long)
2121
}
2222

23-
override fun string(string: String, index: Int, name: String?) {
23+
override fun string(string: String?, index: Int, name: String?) {
2424
statement.bindString(bindIndex(index, name), string)
2525
}
2626

SQLager/src/commonMain/kotlin/co/touchlab/sqlager/user/Database.kt

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,64 @@
11
package co.touchlab.sqlager.user
22

33
import co.touchlab.sqliter.DatabaseManager
4+
import co.touchlab.stately.annotation.ThreadLocal
45
import co.touchlab.stately.collections.AbstractSharedLinkedList
56
import co.touchlab.stately.collections.SharedLinkedList
67
import co.touchlab.stately.collections.frozenLinkedList
7-
import co.touchlab.stately.concurrency.QuickLock
8+
import co.touchlab.stately.concurrency.ReentrantLock
9+
import co.touchlab.stately.concurrency.ThreadLocalRef
810
import co.touchlab.stately.concurrency.withLock
911

1012
class Database(
1113
private val databaseManager: DatabaseManager,
1214
private val cacheSize: Int = 200,
13-
private val instances: Int = 1
15+
instances: Int = 1
1416
) : Operations {
1517

18+
//In-memory databases throw rather than wait if another transaction is happening.
19+
internal val instanceCap = if (databaseManager.configuration.inMemory) {
20+
1
21+
} else {
22+
instances
23+
}
1624
internal val databaseInstances = frozenLinkedList<DatabaseInstance>() as SharedLinkedList<DatabaseInstance>
1725

18-
private val accessLock = QuickLock()
26+
private val accessLock = ReentrantLock()
27+
private val threadLocalDatabaseInstance = ThreadLocalRef<DatabaseInstance>()
28+
29+
/**
30+
* Gets a db instance from the queue. If we're nesting calls, but the user is calling from the outer db,
31+
* return the previously associated instance.
32+
*/
1933
internal inline fun <R> localInstance(block: DatabaseInstance.() -> R): R {
20-
val instanceNode: AbstractSharedLinkedList.Node<DatabaseInstance> = accessLock.withLock {
21-
if (databaseInstances.size < instances) {
22-
val connection = databaseManager.createConnection()
23-
val inst = DatabaseInstance(connection, cacheSize)
24-
databaseInstances.addNode(inst)
25-
} else {
26-
val inst = databaseInstances.get(0)//databaseInstances.removeAt(0)
27-
val node = databaseInstances.nodeIterator().next()
28-
node.readd()
29-
node
34+
val localInstance = threadLocalDatabaseInstance.value
35+
if (localInstance != null) {
36+
return localInstance.block()
37+
} else {
38+
val instanceNode: AbstractSharedLinkedList.Node<DatabaseInstance> = accessLock.withLock {
39+
40+
if (databaseInstances.size < instanceCap) {
41+
val connection = databaseManager.createConnection()
42+
val inst = DatabaseInstance(connection, cacheSize)
43+
databaseInstances.addNode(inst)
44+
} else {
45+
val node = databaseInstances.nodeIterator().next()
46+
node.readd()
47+
node
48+
}
49+
3050
}
31-
}
32-
try {
33-
return instanceNode.nodeValue.block()
34-
} finally {
35-
accessLock.withLock {
36-
instanceNode.remove()
37-
databaseInstances.add(0, instanceNode.nodeValue)
51+
52+
threadLocalDatabaseInstance.value = instanceNode.nodeValue
53+
54+
try {
55+
return instanceNode.nodeValue.block()
56+
} finally {
57+
threadLocalDatabaseInstance.value = null
58+
accessLock.withLock {
59+
// instanceNode.remove()
60+
// databaseInstances.add(0, instanceNode.nodeValue)
61+
}
3862
}
3963
}
4064
}

SQLager/src/commonMain/kotlin/co/touchlab/sqlager/user/DatabaseInstance.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import co.touchlab.sqliter.stringForQuery
66
import co.touchlab.sqliter.withTransaction
77
import co.touchlab.stately.collections.frozenLruCache
88
import co.touchlab.stately.concurrency.AtomicBoolean
9-
import co.touchlab.stately.concurrency.QuickLock
9+
import co.touchlab.stately.concurrency.ReentrantLock
1010
import co.touchlab.stately.concurrency.withLock
1111

1212
internal class DatabaseInstance internal constructor(
@@ -19,12 +19,12 @@ internal class DatabaseInstance internal constructor(
1919
it.value.statement.finalizeStatement()
2020
}
2121

22-
private val accessLock = QuickLock()
22+
private val accessLock = ReentrantLock()
2323
internal inline fun <R> access(block:(DatabaseInstance)->R):R = accessLock.withLock {
2424
block(this)
2525
}
2626

27-
private val cacheLock = QuickLock()
27+
private val cacheLock = ReentrantLock()
2828

2929
override fun execute(sql: String, bind: Binder.() -> Unit) {
3030
safeUseStatement(sql) {
@@ -102,7 +102,6 @@ internal class DatabaseInstance internal constructor(
102102
}
103103

104104
internal fun recycle(statement: BinderStatement) = cacheLock.withLock {
105-
106105
//If connection is closed but an operation is still running, the earlier close call should fail
107106
//Getting here means the outstanding process finished and now we try closing again
108107
if (closed.value) {

SQLager/src/commonMain/kotlin/co/touchlab/sqlager/user/Results.kt

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ import co.touchlab.sqliter.FieldType
55

66
internal class Results constructor(private val cursor: Cursor): Row {
77
fun next(): Boolean = cursor.next()
8-
override fun isNull(index: Int): Boolean = cursor.isNull(index)
9-
override fun string(index: Int): String = cursor.getString(index)
10-
override fun long(index: Int): Long = cursor.getLong(index)
11-
override fun bytes(index: Int): ByteArray = cursor.getBytes(index)
12-
override fun double(index: Int): Double = cursor.getDouble(index)
13-
override fun type(index: Int): FieldType = cursor.getType(index)
8+
override fun isNull(index: Int): Boolean = checkRange(index){cursor.isNull(index)}
9+
override fun string(index: Int): String = checkRange(index){cursor.getString(index)}
10+
override fun long(index: Int): Long = checkRange(index){cursor.getLong(index)}
11+
override fun bytes(index: Int): ByteArray = checkRange(index){cursor.getBytes(index)}
12+
override fun double(index: Int): Double = checkRange(index){cursor.getDouble(index)}
13+
override fun type(index: Int): FieldType = checkRange(index){cursor.getType(index)}
1414
override val columnCount: Int = cursor.columnCount
15-
override fun columnName(index: Int): String = cursor.columnName(index)
15+
override fun columnName(index: Int): String = checkRange(index){cursor.columnName(index)}
1616
override val columnNames: Map<String, Int> = cursor.columnNames
1717

1818
fun iterator():Iterator<Row> = ResultsIterator()
@@ -36,4 +36,14 @@ internal class Results constructor(private val cursor: Cursor): Row {
3636
return this@Results
3737
}
3838
}
39+
40+
private fun <R> checkRange(index:Int, block:()->R):R{
41+
if(index in 0..(columnCount - 1)){
42+
return block()
43+
}else{
44+
throw IndexOutOfBoundsException("Result index $index out of range")
45+
}
46+
}
3947
}
48+
49+

SQLager/src/commonMain/kotlin/co/touchlab/sqlager/user/Row.kt

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package co.touchlab.sqlager.user
22

33
import co.touchlab.sqliter.FieldType
44

5-
interface Row{
5+
interface Row {
66
fun isNull(index: Int): Boolean
77
fun string(index: Int): String
88
fun long(index: Int): Long
@@ -12,4 +12,38 @@ interface Row{
1212
val columnCount: Int
1313
fun columnName(index: Int): String
1414
val columnNames: Map<String, Int>
15-
}
15+
}
16+
17+
fun Row.nameIndex(name: String): Int {
18+
val index = columnNames.get(name)
19+
if (index == null)
20+
throw IllegalArgumentException("Name $name not found in results")
21+
else
22+
return index
23+
}
24+
25+
fun Row.int(index: Int): Int = long(index).toInt()
26+
fun Row.float(index: Int): Float = double(index).toFloat()
27+
28+
fun Row.stringOrNull(index: Int): String? = if(isNull(index)){null}else{string(index)}
29+
fun Row.longOrNull(index: Int): Long? = if(isNull(index)){null}else{long(index)}
30+
fun Row.bytesOrNull(index: Int): ByteArray? = if(isNull(index)){null}else{bytes(index)}
31+
fun Row.doubleOrNull(index: Int): Double? = if(isNull(index)){null}else{double(index)}
32+
fun Row.intOrNull(index: Int): Int? = longOrNull(index)?.toInt()
33+
fun Row.floatOrNull(index: Int): Float? = doubleOrNull(index)?.toFloat()
34+
35+
fun Row.isNull(name: String): Boolean = isNull(nameIndex(name))
36+
37+
fun Row.string(name: String): String = string(nameIndex(name))
38+
fun Row.long(name: String): Long = long(nameIndex(name))
39+
fun Row.bytes(name: String): ByteArray = bytes(nameIndex(name))
40+
fun Row.double(name: String): Double = double(nameIndex(name))
41+
fun Row.int(name: String): Int = long(name).toInt()
42+
fun Row.float(name: String): Float = double(name).toFloat()
43+
44+
fun Row.stringOrNull(name: String): String? = stringOrNull(nameIndex(name))
45+
fun Row.longOrNull(name: String): Long? = longOrNull(nameIndex(name))
46+
fun Row.bytesOrNull(name: String): ByteArray? = bytesOrNull(nameIndex(name))
47+
fun Row.doubleOrNull(name: String): Double? = doubleOrNull(nameIndex(name))
48+
fun Row.intOrNull(name: String): Int? = longOrNull(name)?.toInt()
49+
fun Row.floatOrNull(name: String): Float? = doubleOrNull(name)?.toFloat()
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package co.touchlab.sqlager.user
2+
3+
import kotlin.test.Test
4+
5+
class BinderTest{
6+
@Test
7+
fun namedBindings(){
8+
testDatabase(createSql = "CREATE TABLE test (" +
9+
"ival INTEGER NOT NULL, " +
10+
"dval REAL NOT NULL, " +
11+
"bval BLOB, " +
12+
"sval TEXT NOT NULL)") {database ->
13+
database.insert("insert into test(ival, dval, bval, sval)" +
14+
"values(:ibind, :dbind, :bbind, :sbind)"){
15+
long(123L, name = ":lbind")
16+
double(2.0)
17+
// bytes(it)
18+
string("two")
19+
}
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)