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