1 /*
<lambda>null2  * Copyright 2023 The Android Open Source Project
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 androidx.sqlite.driver
18 
19 import android.database.Cursor
20 import android.database.Cursor.FIELD_TYPE_BLOB
21 import android.database.Cursor.FIELD_TYPE_FLOAT
22 import android.database.Cursor.FIELD_TYPE_INTEGER
23 import android.database.Cursor.FIELD_TYPE_NULL
24 import android.database.Cursor.FIELD_TYPE_STRING
25 import android.database.sqlite.SQLiteCursor
26 import android.database.sqlite.SQLiteDatabase
27 import android.database.sqlite.SQLiteProgram
28 import androidx.sqlite.SQLITE_DATA_BLOB
29 import androidx.sqlite.SQLITE_DATA_FLOAT
30 import androidx.sqlite.SQLITE_DATA_INTEGER
31 import androidx.sqlite.SQLITE_DATA_NULL
32 import androidx.sqlite.SQLITE_DATA_TEXT
33 import androidx.sqlite.SQLiteStatement
34 import androidx.sqlite.driver.ResultCode.SQLITE_MISUSE
35 import androidx.sqlite.driver.ResultCode.SQLITE_RANGE
36 import androidx.sqlite.throwSQLiteException
37 
38 private typealias FrameworkStatement = android.database.sqlite.SQLiteStatement
39 
40 internal sealed class AndroidSQLiteStatement(
41     protected val db: SQLiteDatabase,
42     protected val sql: String,
43 ) : SQLiteStatement {
44 
45     protected var isClosed = false
46 
47     protected fun throwIfClosed() {
48         if (isClosed) {
49             throwSQLiteException(SQLITE_MISUSE, "statement is closed")
50         }
51     }
52 
53     companion object {
54         fun create(db: SQLiteDatabase, sql: String): AndroidSQLiteStatement {
55             return if (isRowStatement(sql)) {
56                 // Statements that return rows (SQLITE_ROW)
57                 SelectAndroidSQLiteStatement(db, sql)
58             } else {
59                 // Statements that don't return row (SQLITE_DONE)
60                 OtherAndroidSQLiteStatement(db, sql)
61             }
62         }
63 
64         private fun isRowStatement(sql: String): Boolean {
65             val prefix = sql.trim()
66             if (prefix.length < 3) {
67                 return false
68             }
69             return when (prefix.substring(0, 3).uppercase()) {
70                 "SEL",
71                 "PRA",
72                 "WIT" -> true
73                 else -> false
74             }
75         }
76     }
77 
78     // TODO(b/304298743): Use android.database.SQLiteRawStatement on Android V+
79     private class SelectAndroidSQLiteStatement(db: SQLiteDatabase, sql: String) :
80         AndroidSQLiteStatement(db, sql) {
81 
82         private var bindingTypes: IntArray = IntArray(0)
83         private var longBindings: LongArray = LongArray(0)
84         private var doubleBindings: DoubleArray = DoubleArray(0)
85         private var stringBindings: Array<String?> = emptyArray()
86         private var blobBindings: Array<ByteArray?> = emptyArray()
87 
88         // TODO(b/307918516): Synchronize
89         private var cursor: Cursor? = null
90 
91         override fun bindBlob(index: Int, value: ByteArray) {
92             throwIfClosed()
93             ensureCapacity(SQLITE_DATA_BLOB, index)
94             bindingTypes[index] = SQLITE_DATA_BLOB
95             blobBindings[index] = value
96         }
97 
98         override fun bindDouble(index: Int, value: Double) {
99             throwIfClosed()
100             ensureCapacity(SQLITE_DATA_FLOAT, index)
101             bindingTypes[index] = SQLITE_DATA_FLOAT
102             doubleBindings[index] = value
103         }
104 
105         override fun bindLong(index: Int, value: Long) {
106             throwIfClosed()
107             ensureCapacity(SQLITE_DATA_INTEGER, index)
108             bindingTypes[index] = SQLITE_DATA_INTEGER
109             longBindings[index] = value
110         }
111 
112         override fun bindText(index: Int, value: String) {
113             throwIfClosed()
114             ensureCapacity(SQLITE_DATA_TEXT, index)
115             bindingTypes[index] = SQLITE_DATA_TEXT
116             stringBindings[index] = value
117         }
118 
119         override fun bindNull(index: Int) {
120             throwIfClosed()
121             ensureCapacity(SQLITE_DATA_NULL, index)
122             bindingTypes[index] = SQLITE_DATA_NULL
123         }
124 
125         override fun getBlob(index: Int): ByteArray {
126             throwIfClosed()
127             val c = throwIfNoRow()
128             throwIfInvalidColumn(c, index)
129             return c.getBlob(index)
130         }
131 
132         override fun getDouble(index: Int): Double {
133             throwIfClosed()
134             val c = throwIfNoRow()
135             throwIfInvalidColumn(c, index)
136             return c.getDouble(index)
137         }
138 
139         override fun getLong(index: Int): Long {
140             throwIfClosed()
141             val c = throwIfNoRow()
142             throwIfInvalidColumn(c, index)
143             return c.getLong(index)
144         }
145 
146         override fun getText(index: Int): String {
147             throwIfClosed()
148             val c = throwIfNoRow()
149             throwIfInvalidColumn(c, index)
150             return c.getString(index)
151         }
152 
153         override fun isNull(index: Int): Boolean {
154             throwIfClosed()
155             val c = throwIfNoRow()
156             throwIfInvalidColumn(c, index)
157             return c.isNull(index)
158         }
159 
160         override fun getColumnCount(): Int {
161             throwIfClosed()
162             ensureCursor()
163             return cursor?.columnCount ?: 0
164         }
165 
166         override fun getColumnName(index: Int): String {
167             throwIfClosed()
168             ensureCursor()
169             val c = checkNotNull(cursor)
170             throwIfInvalidColumn(c, index)
171             return c.getColumnName(index)
172         }
173 
174         override fun getColumnType(index: Int): Int {
175             throwIfClosed()
176             ensureCursor()
177             val c = checkNotNull(cursor)
178             throwIfInvalidColumn(c, index)
179             return c.getDataType(index)
180         }
181 
182         override fun step(): Boolean {
183             throwIfClosed()
184             ensureCursor()
185             return checkNotNull(cursor).moveToNext()
186         }
187 
188         override fun reset() {
189             throwIfClosed()
190             cursor?.close()
191             cursor = null
192         }
193 
194         override fun clearBindings() {
195             throwIfClosed()
196             bindingTypes = IntArray(0)
197             longBindings = LongArray(0)
198             doubleBindings = DoubleArray(0)
199             stringBindings = emptyArray()
200             blobBindings = emptyArray()
201         }
202 
203         override fun close() {
204             if (!isClosed) {
205                 reset()
206             }
207             isClosed = true
208         }
209 
210         private fun ensureCapacity(columnType: Int, index: Int) {
211             val requiredSize = index + 1
212             if (bindingTypes.size < requiredSize) {
213                 bindingTypes = bindingTypes.copyOf(requiredSize)
214             }
215             when (columnType) {
216                 SQLITE_DATA_INTEGER -> {
217                     if (longBindings.size < requiredSize) {
218                         longBindings = longBindings.copyOf(requiredSize)
219                     }
220                 }
221                 SQLITE_DATA_FLOAT -> {
222                     if (doubleBindings.size < requiredSize) {
223                         doubleBindings = doubleBindings.copyOf(requiredSize)
224                     }
225                 }
226                 SQLITE_DATA_TEXT -> {
227                     if (stringBindings.size < requiredSize) {
228                         stringBindings = stringBindings.copyOf(requiredSize)
229                     }
230                 }
231                 SQLITE_DATA_BLOB -> {
232                     if (blobBindings.size < requiredSize) {
233                         blobBindings = blobBindings.copyOf(requiredSize)
234                     }
235                 }
236             }
237         }
238 
239         private fun ensureCursor() {
240             if (cursor == null) {
241                 cursor =
242                     db.rawQueryWithFactory(
243                         /* cursorFactory = */ { _, masterQuery, editTable, query ->
244                             bindTo(query)
245                             SQLiteCursor(masterQuery, editTable, query)
246                         },
247                         /* sql = */ sql,
248                         /* selectionArgs = */ arrayOfNulls(0),
249                         /* editTable = */ null
250                     )
251             }
252         }
253 
254         private fun bindTo(query: SQLiteProgram) {
255             for (index in 1 until bindingTypes.size) {
256                 when (bindingTypes[index]) {
257                     SQLITE_DATA_INTEGER -> query.bindLong(index, longBindings[index])
258                     SQLITE_DATA_FLOAT -> query.bindDouble(index, doubleBindings[index])
259                     SQLITE_DATA_TEXT -> query.bindString(index, stringBindings[index])
260                     SQLITE_DATA_BLOB -> query.bindBlob(index, blobBindings[index])
261                     SQLITE_DATA_NULL -> query.bindNull(index)
262                 }
263             }
264         }
265 
266         private fun throwIfNoRow(): Cursor {
267             return cursor ?: throwSQLiteException(SQLITE_MISUSE, "no row")
268         }
269 
270         private fun throwIfInvalidColumn(c: Cursor, index: Int) {
271             if (index < 0 || index >= c.columnCount) {
272                 throwSQLiteException(SQLITE_RANGE, "column index out of range")
273             }
274         }
275 
276         companion object {
277             private fun Cursor.getDataType(index: Int): Int {
278                 val fieldType = this.getType(index)
279                 return when (this.getType(index)) {
280                     FIELD_TYPE_NULL -> SQLITE_DATA_NULL
281                     FIELD_TYPE_INTEGER -> SQLITE_DATA_INTEGER
282                     FIELD_TYPE_FLOAT -> SQLITE_DATA_FLOAT
283                     FIELD_TYPE_STRING -> SQLITE_DATA_TEXT
284                     FIELD_TYPE_BLOB -> SQLITE_DATA_BLOB
285                     else -> error("Unknown field type: $fieldType")
286                 }
287             }
288         }
289     }
290 
291     private class OtherAndroidSQLiteStatement(db: SQLiteDatabase, sql: String) :
292         AndroidSQLiteStatement(db, sql) {
293 
294         private val delegate: FrameworkStatement = db.compileStatement(sql)
295 
296         override fun bindBlob(index: Int, value: ByteArray) {
297             throwIfClosed()
298             delegate.bindBlob(index, value)
299         }
300 
301         override fun bindDouble(index: Int, value: Double) {
302             throwIfClosed()
303             delegate.bindDouble(index, value)
304         }
305 
306         override fun bindLong(index: Int, value: Long) {
307             throwIfClosed()
308             delegate.bindLong(index, value)
309         }
310 
311         override fun bindText(index: Int, value: String) {
312             throwIfClosed()
313             delegate.bindString(index, value)
314         }
315 
316         override fun bindNull(index: Int) {
317             throwIfClosed()
318             delegate.bindNull(index)
319         }
320 
321         override fun getBlob(index: Int): ByteArray {
322             throwIfClosed()
323             throwSQLiteException(SQLITE_MISUSE, "no row")
324         }
325 
326         override fun getDouble(index: Int): Double {
327             throwIfClosed()
328             throwSQLiteException(SQLITE_MISUSE, "no row")
329         }
330 
331         override fun getLong(index: Int): Long {
332             throwIfClosed()
333             throwSQLiteException(SQLITE_MISUSE, "no row")
334         }
335 
336         override fun getText(index: Int): String {
337             throwIfClosed()
338             throwSQLiteException(SQLITE_MISUSE, "no row")
339         }
340 
341         override fun isNull(index: Int): Boolean {
342             throwIfClosed()
343             throwSQLiteException(SQLITE_MISUSE, "no row")
344         }
345 
346         override fun getColumnCount(): Int {
347             throwIfClosed()
348             return 0
349         }
350 
351         override fun getColumnName(index: Int): String {
352             throwIfClosed()
353             throwSQLiteException(SQLITE_MISUSE, "no row")
354         }
355 
356         override fun getColumnType(index: Int): Int {
357             throwIfClosed()
358             throwSQLiteException(SQLITE_MISUSE, "no row")
359         }
360 
361         override fun step(): Boolean {
362             throwIfClosed()
363             delegate.execute()
364             return false // Statement never returns a row.
365         }
366 
367         override fun reset() {
368             // Android executes and releases non-query statements, so there is nothing to 'reset'.
369         }
370 
371         override fun clearBindings() {
372             throwIfClosed()
373             delegate.clearBindings()
374         }
375 
376         override fun close() {
377             delegate.close()
378             isClosed = true
379         }
380     }
381 }
382