From 09ee95806187329e8dbb57a977487c47252f9a82 Mon Sep 17 00:00:00 2001 From: Alan Hughes <30924086+alanjhughes@users.noreply.github.com> Date: Tue, 27 Jun 2023 14:44:06 +0100 Subject: [PATCH] [android][sqlite] Migrate to Expo Modules API (#23115) --- .../exp/exponent/ExperiencePackagePicker.kt | 4 +- packages/expo-sqlite/CHANGELOG.md | 2 + .../java/expo/modules/sqlite/SQLExceptions.kt | 15 ++++ .../java/expo/modules/sqlite/SQLRecords.kt | 11 +++ .../java/expo/modules/sqlite/SQLiteHelpers.kt | 3 +- .../java/expo/modules/sqlite/SQLiteModule.kt | 76 ++++++++----------- .../java/expo/modules/sqlite/SQLitePackage.kt | 11 --- packages/expo-sqlite/build/SQLite.d.ts.map | 2 +- packages/expo-sqlite/build/SQLite.js | 7 +- packages/expo-sqlite/build/SQLite.js.map | 2 +- packages/expo-sqlite/expo-module.config.json | 3 + packages/expo-sqlite/src/SQLite.ts | 9 ++- 12 files changed, 82 insertions(+), 63 deletions(-) create mode 100644 packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLExceptions.kt create mode 100644 packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLRecords.kt delete mode 100644 packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLitePackage.kt diff --git a/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt b/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt index 1a62f1e50e54bb..a32c18691d0293 100644 --- a/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt +++ b/android/expoview/src/main/java/versioned/host/exp/exponent/ExperiencePackagePicker.kt @@ -57,7 +57,7 @@ import expo.modules.sms.SMSModule import expo.modules.speech.SpeechPackage import expo.modules.splashscreen.SplashScreenModule import expo.modules.splashscreen.SplashScreenPackage -import expo.modules.sqlite.SQLitePackage +import expo.modules.sqlite.SQLiteModule import expo.modules.storereview.StoreReviewModule import expo.modules.systemui.SystemUIModule import expo.modules.systemui.SystemUIPackage @@ -87,7 +87,6 @@ object ExperiencePackagePicker : ModulesProvider { NavigationBarPackage(), NotificationsPackage(), PermissionsPackage(), - SQLitePackage(), SecureStorePackage(), SensorsPackage(), SpeechPackage(), @@ -142,6 +141,7 @@ object ExperiencePackagePicker : ModulesProvider { SharingModule::class.java, SplashScreenModule::class.java, StoreReviewModule::class.java, + SQLiteModule::class.java, SystemUIModule::class.java, VideoThumbnailsModule::class.java, VideoViewModule::class.java, diff --git a/packages/expo-sqlite/CHANGELOG.md b/packages/expo-sqlite/CHANGELOG.md index 7c5ec1cef16059..248bf8d4fec1ae 100644 --- a/packages/expo-sqlite/CHANGELOG.md +++ b/packages/expo-sqlite/CHANGELOG.md @@ -6,6 +6,8 @@ ### 🎉 New features +- Migrated Android codebase to Expo Modules API. ([#23115](https://github.com/expo/expo/pull/23115) by [@alanjhughes](https://github.com/alanjhughes)) + ### 🐛 Bug fixes ### 💡 Others diff --git a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLExceptions.kt b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLExceptions.kt new file mode 100644 index 00000000000000..31b09a5234beff --- /dev/null +++ b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLExceptions.kt @@ -0,0 +1,15 @@ +package expo.modules.sqlite + +import expo.modules.kotlin.exception.CodedException + +class OpenDatabaseException(name: String) : + CodedException("Unable to delete database '$name' that is currently open. Close it prior to deletion.") + +class DatabaseNotFoundException(name: String) : + CodedException("Database '$name' not found") + +class DeleteDatabaseException(name: String) : + CodedException("Unable to delete the database file for '$name' database") + +class SQLiteException(message: String?, cause: Throwable?) : + CodedException(SQLiteModule::class.java.simpleName, message, cause) diff --git a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLRecords.kt b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLRecords.kt new file mode 100644 index 00000000000000..8038e5aa8ecea9 --- /dev/null +++ b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLRecords.kt @@ -0,0 +1,11 @@ +package expo.modules.sqlite + +import expo.modules.kotlin.records.Field +import expo.modules.kotlin.records.Record + +data class Query( + @Field + val sql: String, + @Field + val args: List +) : Record diff --git a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteHelpers.kt b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteHelpers.kt index 6e9bad82d2229e..1e966cf11c5c9e 100644 --- a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteHelpers.kt +++ b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteHelpers.kt @@ -2,7 +2,6 @@ package expo.modules.sqlite import java.io.File import java.io.IOException -import java.util.ArrayList @Throws(IOException::class) internal fun ensureDirExists(dir: File): File { @@ -77,7 +76,7 @@ private fun startsWithCaseInsensitive(str: String, substr: String): Boolean { return str.trimStart().startsWith(substr, true) } -internal fun convertParamsToStringArray(paramArrayArg: ArrayList): Array { +internal fun convertParamsToStringArray(paramArrayArg: List): Array { return paramArrayArg.map { param -> when (param) { is String -> unescapeBlob(param) diff --git a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt index ce990ceeef34a2..49953bcfbcec87 100644 --- a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt +++ b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt @@ -4,31 +4,30 @@ package expo.modules.sqlite import android.content.Context import android.database.Cursor import android.database.sqlite.SQLiteDatabase -import expo.modules.core.ExportedModule -import expo.modules.core.Promise -import expo.modules.core.interfaces.ExpoMethod +import expo.modules.kotlin.exception.Exceptions +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition import java.io.File import java.io.IOException import java.util.* -private val TAG = SQLiteModule::class.java.simpleName -private val EMPTY_ROWS = arrayOf>() -private val EMPTY_COLUMNS = arrayOf() +private val EMPTY_ROWS = emptyArray>() +private val EMPTY_COLUMNS = emptyArray() private val EMPTY_RESULT = SQLiteModule.SQLitePluginResult(EMPTY_ROWS, EMPTY_COLUMNS, 0, 0, null) private val DATABASES: MutableMap = HashMap() -class SQLiteModule(private val mContext: Context) : ExportedModule(mContext) { - override fun getName(): String { - return "ExpoSQLite" - } +class SQLiteModule : Module() { + private val context: Context + get() = appContext.reactContext ?: throw Exceptions.ReactContextLost() + + override fun definition() = ModuleDefinition { + Name("ExpoSQLite") - @ExpoMethod - fun exec(dbName: String, queries: ArrayList>, readOnly: Boolean, promise: Promise) { - try { + AsyncFunction("exec") { dbName: String, queries: List, readOnly: Boolean -> val db = getDatabase(dbName) val results = queries.map { sqlQuery -> - val sql = sqlQuery[0] as String - val bindArgs = convertParamsToStringArray(sqlQuery[1] as ArrayList) + val sql = sqlQuery.sql + val bindArgs = convertParamsToStringArray(sqlQuery.args) try { if (isSelect(sql)) { doSelectInBackgroundAndPossiblyThrow(sql, bindArgs, db) @@ -43,37 +42,27 @@ class SQLiteModule(private val mContext: Context) : ExportedModule(mContext) { SQLitePluginResult(EMPTY_ROWS, EMPTY_COLUMNS, 0, 0, e) } } - val data = pluginResultsToPrimitiveData(results) - promise.resolve(data) - } catch (e: Exception) { - promise.reject("SQLiteError", e) + return@AsyncFunction pluginResultsToPrimitiveData(results) } - } - @ExpoMethod - fun close(dbName: String, promise: Promise) { - DATABASES - .remove(dbName) - ?.close() - promise.resolve(null) - } - - @ExpoMethod - fun deleteAsync(dbName: String, promise: Promise) { - val errorCode = "SQLiteError" - if (DATABASES.containsKey(dbName)) { - promise.reject(errorCode, "Unable to delete database '$dbName' that is currently open. Close it prior to deletion.") - } - val dbFile = File(pathForDatabaseName(dbName)) - if (!dbFile.exists()) { - promise.reject(errorCode, "Database '$dbName' not found") - return + AsyncFunction("close") { dbName: String -> + DATABASES + .remove(dbName) + ?.close() } - if (!dbFile.delete()) { - promise.reject(errorCode, "Unable to delete the database file for '$dbName' database") - return + + AsyncFunction("deleteAsync") { dbName: String -> + if (DATABASES.containsKey(dbName)) { + throw OpenDatabaseException(dbName) + } + val dbFile = File(pathForDatabaseName(dbName)) + if (!dbFile.exists()) { + throw DatabaseNotFoundException(dbName) + } + if (!dbFile.delete()) { + throw DeleteDatabaseException(dbName) + } } - promise.resolve(null) } // do a update/delete/insert operation @@ -143,6 +132,7 @@ class SQLiteModule(private val mContext: Context) : ExportedModule(mContext) { // convert byte[] to binary string; it's good enough, because // WebSQL doesn't support blobs anyway String(cursor.getBlob(index)) + Cursor.FIELD_TYPE_STRING -> cursor.getString(index) else -> null } @@ -150,7 +140,7 @@ class SQLiteModule(private val mContext: Context) : ExportedModule(mContext) { @Throws(IOException::class) private fun pathForDatabaseName(name: String): String { - val directory = File("${mContext.filesDir}${File.separator}SQLite") + val directory = File("${context.filesDir}${File.separator}SQLite") ensureDirExists(directory) return "$directory${File.separator}$name" } diff --git a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLitePackage.kt b/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLitePackage.kt deleted file mode 100644 index 689f073de64ea2..00000000000000 --- a/packages/expo-sqlite/android/src/main/java/expo/modules/sqlite/SQLitePackage.kt +++ /dev/null @@ -1,11 +0,0 @@ -package expo.modules.sqlite - -import android.content.Context -import expo.modules.core.BasePackage -import expo.modules.core.ExportedModule - -class SQLitePackage : BasePackage() { - override fun createExportedModules(context: Context): List { - return listOf(SQLiteModule(context)) - } -} diff --git a/packages/expo-sqlite/build/SQLite.d.ts.map b/packages/expo-sqlite/build/SQLite.d.ts.map index 6b8b48242e9941..2f0f41a2b15821 100644 --- a/packages/expo-sqlite/build/SQLite.d.ts.map +++ b/packages/expo-sqlite/build/SQLite.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"SQLite.d.ts","sourceRoot":"","sources":["../src/SQLite.ts"],"names":[],"mappings":"AAAA,OAAO,oBAAoB,CAAC;AAM5B,OAAO,EAAoD,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAuFlG;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,MAAc,EACvB,WAAW,GAAE,MAAa,EAC1B,IAAI,GAAE,MAAU,EAChB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,cAAc,KAAK,IAAI,GACtC,cAAc,CAShB"} \ No newline at end of file +{"version":3,"file":"SQLite.d.ts","sourceRoot":"","sources":["../src/SQLite.ts"],"names":[],"mappings":"AAAA,OAAO,oBAAoB,CAAC;AAM5B,OAAO,EAAoD,cAAc,EAAE,MAAM,gBAAgB,CAAC;AA4FlG;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,MAAc,EACvB,WAAW,GAAE,MAAa,EAC1B,IAAI,GAAE,MAAU,EAChB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,cAAc,KAAK,IAAI,GACtC,cAAc,CAShB"} \ No newline at end of file diff --git a/packages/expo-sqlite/build/SQLite.js b/packages/expo-sqlite/build/SQLite.js index a04c517301b0fe..ee57cc9f225210 100644 --- a/packages/expo-sqlite/build/SQLite.js +++ b/packages/expo-sqlite/build/SQLite.js @@ -39,7 +39,12 @@ class SQLiteDatabase { } } function _serializeQuery(query) { - return [query.sql, Platform.OS === 'android' ? query.args.map(_escapeBlob) : query.args]; + return Platform.OS === 'android' + ? { + sql: query.sql, + args: query.args.map(_escapeBlob), + } + : [query.sql, query.args]; } function _deserializeResultSet(nativeResult) { const [errorMessage, insertId, rowsAffected, columns, rows] = nativeResult; diff --git a/packages/expo-sqlite/build/SQLite.js.map b/packages/expo-sqlite/build/SQLite.js.map index 52df91f33ea759..ea92503561d97f 100644 --- a/packages/expo-sqlite/build/SQLite.js.map +++ b/packages/expo-sqlite/build/SQLite.js.map @@ -1 +1 @@ -{"version":3,"file":"SQLite.js","sourceRoot":"","sources":["../src/SQLite.ts"],"names":[],"mappings":"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","sourcesContent":["import './polyfillNextTick';\n\nimport customOpenDatabase from '@expo/websql/custom';\nimport { requireNativeModule } from 'expo-modules-core';\nimport { Platform } from 'react-native';\n\nimport { Query, ResultSet, ResultSetError, SQLiteCallback, WebSQLDatabase } from './SQLite.types';\n\nconst ExpoSQLite = requireNativeModule('ExpoSQLite');\n\nfunction zipObject(keys: string[], values: any[]) {\n const result = {};\n for (let i = 0; i < keys.length; i++) {\n result[keys[i]] = values[i];\n }\n return result;\n}\n\nclass SQLiteDatabase {\n _name: string;\n _closed: boolean = false;\n\n constructor(name: string) {\n this._name = name;\n }\n\n exec(queries: Query[], readOnly: boolean, callback: SQLiteCallback): void {\n if (this._closed) {\n throw new Error(`The SQLite database is closed`);\n }\n\n ExpoSQLite.exec(this._name, queries.map(_serializeQuery), readOnly).then(\n (nativeResultSets) => {\n callback(null, nativeResultSets.map(_deserializeResultSet));\n },\n (error) => {\n // TODO: make the native API consistently reject with an error, not a string or other type\n callback(error instanceof Error ? error : new Error(error));\n }\n );\n }\n\n close() {\n this._closed = true;\n return ExpoSQLite.close(this._name);\n }\n\n deleteAsync(): Promise {\n if (!this._closed) {\n throw new Error(\n `Unable to delete '${this._name}' database that is currently open. Close it prior to deletion.`\n );\n }\n\n return ExpoSQLite.deleteAsync(this._name);\n }\n}\n\nfunction _serializeQuery(query: Query): [string, unknown[]] {\n return [query.sql, Platform.OS === 'android' ? query.args.map(_escapeBlob) : query.args];\n}\n\nfunction _deserializeResultSet(nativeResult): ResultSet | ResultSetError {\n const [errorMessage, insertId, rowsAffected, columns, rows] = nativeResult;\n // TODO: send more structured error information from the native module so we can better construct\n // a SQLException object\n if (errorMessage !== null) {\n return { error: new Error(errorMessage) } as ResultSetError;\n }\n\n return {\n insertId,\n rowsAffected,\n rows: rows.map((row) => zipObject(columns, row)),\n };\n}\n\nfunction _escapeBlob(data: T): T {\n if (typeof data === 'string') {\n /* eslint-disable no-control-regex */\n return data\n .replace(/\\u0002/g, '\\u0002\\u0002')\n .replace(/\\u0001/g, '\\u0001\\u0002')\n .replace(/\\u0000/g, '\\u0001\\u0001') as any;\n /* eslint-enable no-control-regex */\n } else {\n return data;\n }\n}\n\nconst _openExpoSQLiteDatabase = customOpenDatabase(SQLiteDatabase);\n\n// @needsAudit @docsMissing\n/**\n * Open a database, creating it if it doesn't exist, and return a `Database` object. On disk,\n * the database will be created under the app's [documents directory](./filesystem), i.e.\n * `${FileSystem.documentDirectory}/SQLite/${name}`.\n * > The `version`, `description` and `size` arguments are ignored, but are accepted by the function\n * for compatibility with the WebSQL specification.\n * @param name Name of the database file to open.\n * @param version\n * @param description\n * @param size\n * @param callback\n * @return\n */\nexport function openDatabase(\n name: string,\n version: string = '1.0',\n description: string = name,\n size: number = 1,\n callback?: (db: WebSQLDatabase) => void\n): WebSQLDatabase {\n if (name === undefined) {\n throw new TypeError(`The database name must not be undefined`);\n }\n const db = _openExpoSQLiteDatabase(name, version, description, size, callback);\n db.exec = db._db.exec.bind(db._db);\n db.closeAsync = db._db.close.bind(db._db);\n db.deleteAsync = db._db.deleteAsync.bind(db._db);\n return db;\n}\n"]} \ No newline at end of file +{"version":3,"file":"SQLite.js","sourceRoot":"","sources":["../src/SQLite.ts"],"names":[],"mappings":"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","sourcesContent":["import './polyfillNextTick';\n\nimport customOpenDatabase from '@expo/websql/custom';\nimport { requireNativeModule } from 'expo-modules-core';\nimport { Platform } from 'react-native';\n\nimport { Query, ResultSet, ResultSetError, SQLiteCallback, WebSQLDatabase } from './SQLite.types';\n\nconst ExpoSQLite = requireNativeModule('ExpoSQLite');\n\nfunction zipObject(keys: string[], values: any[]) {\n const result = {};\n for (let i = 0; i < keys.length; i++) {\n result[keys[i]] = values[i];\n }\n return result;\n}\n\nclass SQLiteDatabase {\n _name: string;\n _closed: boolean = false;\n\n constructor(name: string) {\n this._name = name;\n }\n\n exec(queries: Query[], readOnly: boolean, callback: SQLiteCallback): void {\n if (this._closed) {\n throw new Error(`The SQLite database is closed`);\n }\n\n ExpoSQLite.exec(this._name, queries.map(_serializeQuery), readOnly).then(\n (nativeResultSets) => {\n callback(null, nativeResultSets.map(_deserializeResultSet));\n },\n (error) => {\n // TODO: make the native API consistently reject with an error, not a string or other type\n callback(error instanceof Error ? error : new Error(error));\n }\n );\n }\n\n close() {\n this._closed = true;\n return ExpoSQLite.close(this._name);\n }\n\n deleteAsync(): Promise {\n if (!this._closed) {\n throw new Error(\n `Unable to delete '${this._name}' database that is currently open. Close it prior to deletion.`\n );\n }\n\n return ExpoSQLite.deleteAsync(this._name);\n }\n}\n\nfunction _serializeQuery(query: Query): Query | [string, any[]] {\n return Platform.OS === 'android'\n ? {\n sql: query.sql,\n args: query.args.map(_escapeBlob),\n }\n : [query.sql, query.args];\n}\n\nfunction _deserializeResultSet(nativeResult): ResultSet | ResultSetError {\n const [errorMessage, insertId, rowsAffected, columns, rows] = nativeResult;\n // TODO: send more structured error information from the native module so we can better construct\n // a SQLException object\n if (errorMessage !== null) {\n return { error: new Error(errorMessage) } as ResultSetError;\n }\n\n return {\n insertId,\n rowsAffected,\n rows: rows.map((row) => zipObject(columns, row)),\n };\n}\n\nfunction _escapeBlob(data: T): T {\n if (typeof data === 'string') {\n /* eslint-disable no-control-regex */\n return data\n .replace(/\\u0002/g, '\\u0002\\u0002')\n .replace(/\\u0001/g, '\\u0001\\u0002')\n .replace(/\\u0000/g, '\\u0001\\u0001') as any;\n /* eslint-enable no-control-regex */\n } else {\n return data;\n }\n}\n\nconst _openExpoSQLiteDatabase = customOpenDatabase(SQLiteDatabase);\n\n// @needsAudit @docsMissing\n/**\n * Open a database, creating it if it doesn't exist, and return a `Database` object. On disk,\n * the database will be created under the app's [documents directory](./filesystem), i.e.\n * `${FileSystem.documentDirectory}/SQLite/${name}`.\n * > The `version`, `description` and `size` arguments are ignored, but are accepted by the function\n * for compatibility with the WebSQL specification.\n * @param name Name of the database file to open.\n * @param version\n * @param description\n * @param size\n * @param callback\n * @return\n */\nexport function openDatabase(\n name: string,\n version: string = '1.0',\n description: string = name,\n size: number = 1,\n callback?: (db: WebSQLDatabase) => void\n): WebSQLDatabase {\n if (name === undefined) {\n throw new TypeError(`The database name must not be undefined`);\n }\n const db = _openExpoSQLiteDatabase(name, version, description, size, callback);\n db.exec = db._db.exec.bind(db._db);\n db.closeAsync = db._db.close.bind(db._db);\n db.deleteAsync = db._db.deleteAsync.bind(db._db);\n return db;\n}\n"]} \ No newline at end of file diff --git a/packages/expo-sqlite/expo-module.config.json b/packages/expo-sqlite/expo-module.config.json index 2f0213652b3d74..5358c28169620b 100644 --- a/packages/expo-sqlite/expo-module.config.json +++ b/packages/expo-sqlite/expo-module.config.json @@ -3,5 +3,8 @@ "platforms": ["ios", "android"], "ios": { "modules": ["SQLiteModule"] + }, + "android": { + "modules": ["expo.modules.sqlite.SQLiteModule"] } } diff --git a/packages/expo-sqlite/src/SQLite.ts b/packages/expo-sqlite/src/SQLite.ts index 1af28c41532f9c..44b34550df7dda 100644 --- a/packages/expo-sqlite/src/SQLite.ts +++ b/packages/expo-sqlite/src/SQLite.ts @@ -56,8 +56,13 @@ class SQLiteDatabase { } } -function _serializeQuery(query: Query): [string, unknown[]] { - return [query.sql, Platform.OS === 'android' ? query.args.map(_escapeBlob) : query.args]; +function _serializeQuery(query: Query): Query | [string, any[]] { + return Platform.OS === 'android' + ? { + sql: query.sql, + args: query.args.map(_escapeBlob), + } + : [query.sql, query.args]; } function _deserializeResultSet(nativeResult): ResultSet | ResultSetError {