1+ /*
2+ * Copyright (C) 2018 Touchlab, Inc.
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+ package co.touchlab.sqliter.sqldelight
18+
19+ import co.touchlab.sqliter.*
20+ import co.touchlab.stately.collections.AbstractSharedLinkedList
21+ import co.touchlab.stately.collections.SharedLinkedList
22+ import co.touchlab.stately.collections.frozenHashMap
23+ import co.touchlab.stately.collections.frozenLinkedList
24+ import co.touchlab.stately.concurrency.AtomicReference
25+ import co.touchlab.stately.concurrency.ReentrantLock
26+ import co.touchlab.stately.concurrency.ThreadRef
27+ import co.touchlab.stately.concurrency.withLock
28+ import co.touchlab.stately.freeze
29+ import com.squareup.sqldelight.Transacter
30+ import com.squareup.sqldelight.db.SqlCursor
31+ import com.squareup.sqldelight.db.SqlDatabase
32+ import com.squareup.sqldelight.db.SqlDatabaseConnection
33+ import com.squareup.sqldelight.db.SqlPreparedStatement
34+
35+ class SqlLighterDatabase (private val databaseManager : DatabaseManager ) : SqlDatabase {
36+ private val connectionInstanceThreadCache = frozenLinkedList<ThreadConnection >()
37+
38+ /* *
39+ * Keep all outstanding cursors to close when closing the db, just in case the user didn't.
40+ */
41+ internal val cursorCollection = frozenLinkedList<Cursor >() as SharedLinkedList <Cursor >
42+ private val connectionLock = ReentrantLock ()
43+ private val singleOpConnection = databaseManager.createMultiThreadedConnection()
44+ private val publicApiConnection = SqlLighterConnection (this )
45+
46+ override fun close () = connectionLock.withLock {
47+ connectionInstanceThreadCache.forEach {
48+ it.connection.close()
49+ }
50+ connectionInstanceThreadCache.clear()
51+
52+ singleOpConnection.close()
53+ }
54+
55+ override fun getConnection (): SqlDatabaseConnection = publicApiConnection
56+
57+ /* *
58+ * If we're in a transaction, then I have a connection. Otherwise we lock and
59+ * use the open connection on which all other ops run.
60+ */
61+ internal fun <R > realConnectionMineOrUnaligned (block : DatabaseConnection .() -> R ): R {
62+ val mine = myConnection()
63+ return if (mine != null )
64+ mine.connection.block()
65+ else {
66+ connectionLock.withLock {
67+ singleOpConnection.block()
68+ }
69+ }
70+ }
71+
72+ /* *
73+ * If we're in the middle of a transaction, the thread ref will point to us.
74+ */
75+ internal fun myConnection () = connectionInstanceThreadCache.find { it.threadRef.notNullSame() }
76+
77+ internal fun myConnectionOrNew (): ThreadConnection {
78+ return myConnection() ? : connectionLock.withLock {
79+ val empty = connectionInstanceThreadCache.find { it.threadRef == null } ? : createNewPollConnection()
80+ empty.threadRef = ThreadRef ()
81+ empty
82+ }
83+ }
84+
85+ /* *
86+ * All connections tracked in our pool cache
87+ */
88+ private fun createNewPollConnection (): ThreadConnection {
89+ val conn = ThreadConnection (databaseManager.createMultiThreadedConnection())
90+ connectionInstanceThreadCache.add(conn)
91+ return conn
92+ }
93+
94+ internal fun recycleCursor (node : AbstractSharedLinkedList .Node <Cursor >) = connectionLock.withLock {
95+ node.nodeValue.statement.finalizeStatement()
96+ node.remove()
97+ }
98+
99+ internal fun trackCursor (cursor : Cursor ):AbstractSharedLinkedList .Node <Cursor > = connectionLock.withLock {
100+ cursorCollection.addNode(cursor)
101+ }
102+ }
103+
104+ class SqlLighterConnection (private val database : SqlLighterDatabase ) : SqlDatabaseConnection {
105+ override fun currentTransaction (): Transacter .Transaction ? = database.myConnection()?.transaction?.value
106+
107+ override fun newTransaction (): Transacter .Transaction {
108+ val myConn = database.myConnectionOrNew()
109+ return myConn.newTransaction()
110+ }
111+
112+ override fun prepareStatement (sql : String , type : SqlPreparedStatement .Type , parameters : Int ): SqlPreparedStatement =
113+ SqlLighterStatement (sql, type, database)
114+ }
115+
116+ class ThreadConnection (val connection : DatabaseConnection ) {
117+ var threadRef: ThreadRef ? = null
118+ private val transLock = ReentrantLock ()
119+ internal val transaction: AtomicReference <Transaction ?> = AtomicReference (null )
120+
121+ fun newTransaction (): Transaction {
122+ val enclosing = transaction.value
123+
124+ // Create here, in case we bomb...
125+ if (enclosing == null ) {
126+ connection.beginTransaction()
127+ }
128+
129+ val trans = Transaction (enclosing).freeze()
130+ transaction.value = trans
131+
132+ return trans
133+ }
134+
135+ inner class Transaction (
136+ override val enclosingTransaction : Transaction ?
137+ ) : Transacter.Transaction() {
138+
139+ override fun endTransaction (successful : Boolean ) = transLock.withLock {
140+ if (enclosingTransaction == null ) {
141+ if (successful) {
142+ connection.setTransactionSuccessful()
143+ }
144+
145+ connection.endTransaction()
146+ }
147+ transaction.value = enclosingTransaction
148+ }
149+ }
150+ }
151+
152+ internal fun ThreadRef?.notNullSame (): Boolean {
153+ return this != null && same()
154+ }
155+
156+ class SqlLighterStatement (
157+ private val sql : String ,
158+ private val type : SqlPreparedStatement .Type ,
159+ private val database : SqlLighterDatabase
160+ ) : SqlPreparedStatement {
161+
162+ // For each statement declared, there can be an instance per-thread.
163+ private val statementInstanceThreadCache = frozenLinkedList<ThreadStatement >()
164+
165+ private fun myThreadCachedStatementInstance () = statementInstanceThreadCache.find { it.mine }
166+
167+ override fun bindBytes (index : Int , value : ByteArray? ) = myThreadStatementInstance().bindBytes(index, value)
168+ override fun bindDouble (index : Int , value : Double? ) = myThreadStatementInstance().bindDouble(index, value)
169+ override fun bindLong (index : Int , value : Long? ) = myThreadStatementInstance().bindLong(index, value)
170+ override fun bindString (index : Int , value : String? ) = myThreadStatementInstance().bindString(index, value)
171+
172+ /* *
173+ * Executing a statement clears the instance definition. Effectively that means the bindings are reset. We can
174+ * recycle these rather than letting them be deallocated in the future if that improves performance in some way.
175+ */
176+ override fun execute () {
177+ database.realConnectionMineOrUnaligned {
178+ withStatement(sql) {
179+ applyBindings(this )
180+ when (type) {
181+ SqlPreparedStatement .Type .SELECT -> throw AssertionError ()
182+ else -> execute()
183+ }
184+ }
185+ }
186+ removeMyInstance()
187+ }
188+
189+ /* *
190+ * Creating a cursor returns an actual sqlite statement instance, so we need to be careful with these. However,
191+ * the design of sqldelight's queries and the QueryWrapper would like us to retain the bindings (I think), so
192+ * we leave the instance for this thread hanging out.
193+ *
194+ * If the developer creates many threads, this will grow over time. We will probably want to either cap the cache
195+ * count, and risk losing the bindings, or redesign the interface somewhat.
196+ *
197+ * It does seem that executable statements and queries are designed somewhat differently, however.
198+ */
199+ override fun executeQuery (): SqlCursor = database.realConnectionMineOrUnaligned {
200+ val statement = createStatement(sql)
201+ applyBindings(statement)
202+ SQLiterCursor (database.trackCursor(statement.query()), database)
203+ }
204+
205+ private fun applyBindings (statement : Statement ){
206+ myThreadCachedStatementInstance()?.binds?.forEach {
207+ it.value(statement)
208+ }
209+ }
210+
211+ // These don't need to be locked because only *your* thread is adding/removing it's own entries
212+ // Assuming the list itself is thread safe, no problem
213+ private fun myThreadStatementInstance (): ThreadStatement {
214+ return myThreadCachedStatementInstance() ? : createMyInstance()
215+ }
216+
217+ private fun createMyInstance (): ThreadStatement {
218+ val myInstance = ThreadStatement ()
219+ statementInstanceThreadCache.add(myInstance)
220+ return myInstance
221+ }
222+
223+ private fun removeMyInstance () {
224+ val mine = myThreadCachedStatementInstance()
225+ if (mine != null )
226+ statementInstanceThreadCache.remove(mine)
227+ }
228+ }
229+
230+ class SQLiterCursor (private val cursorCollectionNode : AbstractSharedLinkedList .Node <Cursor >, private val database : SqlLighterDatabase ) : SqlCursor {
231+ private val cursor = cursorCollectionNode.nodeValue
232+
233+ override fun close () {
234+ database.recycleCursor(cursorCollectionNode)
235+ }
236+
237+ override fun getBytes (index : Int ): ByteArray? = cursor.getBytesOrNull(index)
238+
239+ override fun getDouble (index : Int ): Double? = cursor.getDoubleOrNull(index)
240+
241+ override fun getLong (index : Int ): Long? = cursor.getLongOrNull(index)
242+
243+ override fun getString (index : Int ): String? = cursor.getStringOrNull(index)
244+
245+ override fun next (): Boolean = cursor.next()
246+ }
247+
248+ class ThreadStatement {
249+ private val threadRef = ThreadRef ()
250+ internal val binds = frozenHashMap<Int , (Statement ) - > Unit > ()
251+
252+ val mine: Boolean
253+ get() = threadRef.same()
254+
255+ fun bindBytes (index : Int , value : ByteArray? ) {
256+ binds.put(index) { it.bindBlob(index, value) }
257+ }
258+
259+ fun bindDouble (index : Int , value : Double? ) {
260+ binds.put(index) { it.bindDouble(index, value) }
261+ }
262+
263+ fun bindLong (index : Int , value : Long? ) {
264+ binds.put(index) { it.bindLong(index, value) }
265+ }
266+
267+ fun bindString (index : Int , value : String? ) {
268+ binds.put(index) { it.bindString(index, value) }
269+ }
270+ }
0 commit comments