forked from touchlab/SQLiter
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNativeDatabaseManager.kt
More file actions
125 lines (107 loc) · 5.13 KB
/
NativeDatabaseManager.kt
File metadata and controls
125 lines (107 loc) · 5.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
* Copyright (C) 2018 Touchlab, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package co.touchlab.sqliter.native
import co.touchlab.sqliter.*
import co.touchlab.sqliter.concurrency.ConcurrentDatabaseConnection
import co.touchlab.sqliter.concurrency.Lock
import co.touchlab.sqliter.concurrency.SingleThreadDatabaseConnection
import co.touchlab.sqliter.concurrency.withLock
import co.touchlab.sqliter.interop.OpenFlags
import co.touchlab.sqliter.interop.dbOpen
import co.touchlab.sqliter.util.maybeFreeze
import kotlin.concurrent.AtomicInt
class NativeDatabaseManager(private val path:String,
override val configuration: DatabaseConfiguration
): DatabaseManager {
override fun createMultiThreadedConnection(): DatabaseConnection {
return ConcurrentDatabaseConnection(createConnection()).maybeFreeze()
}
override fun createSingleThreadedConnection(): DatabaseConnection {
return SingleThreadDatabaseConnection(createConnection())
}
private val lock = Lock()
private val newConnection = AtomicInt(0)
private fun createConnection(): DatabaseConnection {
return lock.withLock {
val connectionPtrArg = dbOpen(
path,
listOf(OpenFlags.CREATE_IF_NECESSARY),
"sqliter",
false,
false,
configuration.extendedConfig.lookasideSlotSize,
configuration.extendedConfig.lookasideSlotCount,
configuration.extendedConfig.busyTimeout,
configuration.loggingConfig.logger,
configuration.loggingConfig.verboseDataCalls
)
val conn = NativeDatabaseConnection(this, connectionPtrArg)
configuration.lifecycleConfig.onCreateConnection(conn)
if (configuration.extendedConfig.synchronousFlag != null) {
conn.updateSynchronousFlag(configuration.extendedConfig.synchronousFlag)
}
if (configuration.encryptionConfig.rekey == null) {
configuration.encryptionConfig.key?.let { conn.setCipherKey(it) }
} else {
if (configuration.encryptionConfig.key == null) {
// If executed here, it indicate that setCipherKey to `rekey` due to the old key is not set yet.
conn.setCipherKey(configuration.encryptionConfig.rekey)
} else {
conn.resetCipherKey(configuration.encryptionConfig.key, configuration.encryptionConfig.rekey)
}
}
// These flags should be explicitly set on each connection at all times.
//
// "should set the foreign key enforcement flag [...] and not depend on the default setting."
// https://www.sqlite.org/pragma.html#pragma_foreign_keys
// "Recursive triggers may be turned on by default in future versions of SQLite."
// https://www.sqlite.org/pragma.html#pragma_recursive_triggers
conn.updateForeignKeyConstraints(configuration.extendedConfig.foreignKeyConstraints)
conn.updateRecursiveTriggers(configuration.extendedConfig.recursiveTriggers)
if(newConnection.value == 0){
conn.updateJournalMode(configuration.journalMode)
try {
val version = configuration.version
if(version != NO_VERSION_CHECK)
conn.migrateIfNeeded(configuration.create, configuration.upgrade, configuration.downgrade, version)
} catch (e: Exception) {
// If this failed, we have to close the connection or we will end up leaking it.
println("attempted to run migration and failed. closing connection.")
conn.close()
throw e
}
// "Temporary" and "purely in-memory" databases live only as long
// as the connection. Subsequent connections (even if open at
// the same time) are completely separate databases.
//
// If this is the case, do not increment newConnection so that
// this if block executes on every new connection (i.e. every new
// ephemeral database).
when (path) {
"", ":memory:" -> {}
else -> newConnection.increment()
}
}
conn
}
}
internal fun closeConnection(connection:DatabaseConnection){
configuration.lifecycleConfig.onCloseConnection(connection)
}
}
fun AtomicInt.increment() {
incrementAndGet()
}