expo-sqlite 11.8.0 → 12.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/android/CMakeLists.txt +2 -2
  3. package/android/build.gradle +52 -5
  4. package/android/src/main/cpp/NativeDatabaseBinding.cpp +142 -0
  5. package/android/src/main/cpp/NativeDatabaseBinding.h +75 -0
  6. package/android/src/main/cpp/NativeStatementBinding.cpp +157 -0
  7. package/android/src/main/cpp/NativeStatementBinding.h +83 -0
  8. package/android/src/main/cpp/SQLite3Main.cpp +7 -2
  9. package/android/src/main/cpp/SQLite3Wrapper.cpp +0 -30
  10. package/android/src/main/cpp/SQLite3Wrapper.h +0 -4
  11. package/android/src/main/java/expo/modules/sqlite/NativeDatabase.kt +11 -0
  12. package/android/src/main/java/expo/modules/sqlite/NativeDatabaseBinding.kt +79 -0
  13. package/android/src/main/java/expo/modules/sqlite/NativeStatement.kt +11 -0
  14. package/android/src/main/java/expo/modules/sqlite/NativeStatementBinding.kt +41 -0
  15. package/android/src/main/java/expo/modules/sqlite/SQLExceptions.kt +17 -6
  16. package/android/src/main/java/expo/modules/sqlite/SQLRecords.kt +16 -3
  17. package/android/src/main/java/expo/modules/sqlite/SQLite3Wrapper.kt +0 -30
  18. package/android/src/main/java/expo/modules/sqlite/SQLiteHelpers.kt +0 -79
  19. package/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt +2 -55
  20. package/android/src/main/java/expo/modules/sqlite/SQLiteModuleNext.kt +454 -0
  21. package/android/src/main/java/expo/modules/sqlite/SQLiteOptions.kt +20 -0
  22. package/build/SQLite.d.ts +2 -2
  23. package/build/SQLite.d.ts.map +1 -1
  24. package/build/SQLite.js.map +1 -1
  25. package/build/SQLite.types.d.ts +3 -2
  26. package/build/SQLite.types.d.ts.map +1 -1
  27. package/build/SQLite.types.js.map +1 -1
  28. package/build/next/Database.d.ts +254 -0
  29. package/build/next/Database.d.ts.map +1 -0
  30. package/build/next/Database.js +320 -0
  31. package/build/next/Database.js.map +1 -0
  32. package/build/next/ExpoSQLiteNext.d.ts +12 -0
  33. package/build/next/ExpoSQLiteNext.d.ts.map +1 -0
  34. package/build/next/ExpoSQLiteNext.js +26 -0
  35. package/build/next/ExpoSQLiteNext.js.map +1 -0
  36. package/build/next/ExpoSQLiteNext.native.d.ts +3 -0
  37. package/build/next/ExpoSQLiteNext.native.d.ts.map +1 -0
  38. package/build/next/ExpoSQLiteNext.native.js +3 -0
  39. package/build/next/ExpoSQLiteNext.native.js.map +1 -0
  40. package/build/next/NativeDatabase.d.ts +44 -0
  41. package/build/next/NativeDatabase.d.ts.map +1 -0
  42. package/build/next/NativeDatabase.js +2 -0
  43. package/build/next/NativeDatabase.js.map +1 -0
  44. package/build/next/NativeStatement.d.ts +71 -0
  45. package/build/next/NativeStatement.d.ts.map +1 -0
  46. package/build/next/NativeStatement.js +2 -0
  47. package/build/next/NativeStatement.js.map +1 -0
  48. package/build/next/Statement.d.ts +138 -0
  49. package/build/next/Statement.d.ts.map +1 -0
  50. package/build/next/Statement.js +184 -0
  51. package/build/next/Statement.js.map +1 -0
  52. package/build/next/hooks.d.ts +35 -0
  53. package/build/next/hooks.d.ts.map +1 -0
  54. package/build/next/hooks.js +60 -0
  55. package/build/next/hooks.js.map +1 -0
  56. package/build/next/index.d.ts +4 -0
  57. package/build/next/index.d.ts.map +1 -0
  58. package/build/next/index.js +4 -0
  59. package/build/next/index.js.map +1 -0
  60. package/expo-module.config.json +2 -2
  61. package/ios/CRSQLiteLoader.h +3 -1
  62. package/ios/CRSQLiteLoader.m +11 -6
  63. package/ios/Exceptions.swift +13 -0
  64. package/ios/ExpoSQLite.podspec +2 -2
  65. package/ios/NativeDatabase.swift +26 -0
  66. package/ios/NativeStatement.swift +13 -0
  67. package/ios/SQLAction.swift +23 -0
  68. package/ios/SQLiteModule.swift +0 -53
  69. package/ios/SQLiteModuleNext.swift +555 -0
  70. package/ios/SQLiteOptions.swift +26 -0
  71. package/next.d.ts +1 -0
  72. package/next.js +1 -0
  73. package/package.json +4 -2
  74. package/src/SQLite.ts +2 -1
  75. package/src/SQLite.types.ts +5 -2
  76. package/src/next/Database.ts +442 -0
  77. package/src/next/ExpoSQLiteNext.native.ts +2 -0
  78. package/src/next/ExpoSQLiteNext.ts +34 -0
  79. package/src/next/NativeDatabase.ts +58 -0
  80. package/src/next/NativeStatement.ts +96 -0
  81. package/src/next/Statement.ts +306 -0
  82. package/src/next/hooks.tsx +111 -0
  83. package/src/next/index.ts +3 -0
  84. package/vendor/README.expo.md +0 -6
  85. package/vendor/shell.c +0 -28032
  86. package/vendor/sqlite3.c +0 -247629
  87. package/vendor/sqlite3.h +0 -13068
  88. package/vendor/sqlite3ext.h +0 -709
@@ -25,10 +25,6 @@ public:
25
25
  // sqlite3 bindings
26
26
  int sqlite3_open(const std::string &dbPath);
27
27
  int sqlite3_close();
28
- int sqlite3_enable_load_extension(int onoff);
29
- int sqlite3_load_extension(const std::string &libPath,
30
- const std::string &entryProc);
31
- void sqlite3_update_hook(bool enabled);
32
28
 
33
29
  private:
34
30
  explicit SQLite3Wrapper(jni::alias_ref<SQLite3Wrapper::jhybridobject> jThis)
@@ -0,0 +1,11 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ package expo.modules.sqlite
4
+
5
+ import expo.modules.kotlin.sharedobjects.SharedRef
6
+
7
+ internal class NativeDatabase(val dbName: String, val openOptions: OpenDatabaseOptions) : SharedRef<NativeDatabaseBinding>(NativeDatabaseBinding()) {
8
+ override fun equals(other: Any?): Boolean {
9
+ return other is NativeDatabase && this.ref == other.ref
10
+ }
11
+ }
@@ -0,0 +1,79 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ package expo.modules.sqlite
4
+
5
+ import com.facebook.jni.HybridData
6
+ import expo.modules.core.interfaces.DoNotStrip
7
+
8
+ private typealias UpdateListener = (dbName: String, tableName: String, operationType: Int, rowID: Long) -> Unit
9
+
10
+ @Suppress("KotlinJniMissingFunction")
11
+ @DoNotStrip
12
+ internal class NativeDatabaseBinding {
13
+ @DoNotStrip
14
+ private val mHybridData: HybridData
15
+
16
+ private var mUpdateListener: UpdateListener? = null
17
+
18
+ init {
19
+ mHybridData = initHybrid()
20
+ }
21
+
22
+ /**
23
+ * Enable data change notifications
24
+ */
25
+ fun enableUpdateHook(listener: UpdateListener) {
26
+ sqlite3_update_hook(true)
27
+ mUpdateListener = listener
28
+ }
29
+
30
+ /**
31
+ * Disable data change notifications
32
+ */
33
+ fun disableUpdateHook() {
34
+ mUpdateListener = null
35
+ sqlite3_update_hook(false)
36
+ }
37
+
38
+ // region sqlite3 bindings
39
+
40
+ external fun sqlite3_changes(): Int
41
+ external fun sqlite3_close(): Int
42
+ external fun sqlite3_db_filename(dbName: String): String
43
+ external fun sqlite3_enable_load_extension(onoff: Int): Int
44
+ external fun sqlite3_exec(source: String): Int
45
+ external fun sqlite3_get_autocommit(): Int
46
+ external fun sqlite3_last_insert_rowid(): Long
47
+ external fun sqlite3_load_extension(libPath: String, entryProc: String): Int
48
+ external fun sqlite3_open(dbPath: String): Int
49
+ external fun sqlite3_prepare_v2(source: String, statement: NativeStatementBinding): Int
50
+ private external fun sqlite3_update_hook(enabled: Boolean) // Keeps it private internally and uses `enableUpdateHook` publicly
51
+
52
+ external fun convertSqlLiteErrorToString(): String
53
+
54
+ // endregion
55
+
56
+ // region internals
57
+
58
+ private external fun initHybrid(): HybridData
59
+
60
+ @Suppress("unused")
61
+ @DoNotStrip
62
+ private fun onUpdate(action: Int, dbName: String, tableName: String, rowId: Long) {
63
+ mUpdateListener?.invoke(dbName, tableName, action, rowId)
64
+ }
65
+
66
+ // endregion
67
+
68
+ companion object {
69
+ init {
70
+ System.loadLibrary("expo-sqlite")
71
+ }
72
+
73
+ // These error code should be synced with sqlite3.h
74
+ const val SQLITE_OK = 0
75
+
76
+ const val SQLITE_ROW = 100
77
+ const val SQLITE_DONE = 101
78
+ }
79
+ }
@@ -0,0 +1,11 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ package expo.modules.sqlite
4
+
5
+ import expo.modules.kotlin.sharedobjects.SharedRef
6
+
7
+ internal class NativeStatement : SharedRef<NativeStatementBinding>(NativeStatementBinding()) {
8
+ override fun equals(other: Any?): Boolean {
9
+ return other is NativeStatement && this.ref == other.ref
10
+ }
11
+ }
@@ -0,0 +1,41 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
3
+ package expo.modules.sqlite
4
+
5
+ import com.facebook.jni.HybridData
6
+ import expo.modules.core.interfaces.DoNotStrip
7
+
8
+ internal typealias ColumnNames = ArrayList<String>
9
+ internal typealias ColumnValues = ArrayList<Any>
10
+
11
+ @Suppress("KotlinJniMissingFunction")
12
+ @DoNotStrip
13
+ internal class NativeStatementBinding {
14
+ @DoNotStrip
15
+ private val mHybridData: HybridData
16
+
17
+ init {
18
+ mHybridData = initHybrid()
19
+ }
20
+
21
+ // region sqlite3 bindings
22
+
23
+ external fun sqlite3_bind_parameter_index(name: String): Int
24
+ external fun sqlite3_column_count(): Int
25
+ external fun sqlite3_column_name(index: Int): String
26
+ external fun sqlite3_finalize(): Int
27
+ external fun sqlite3_reset(): Int
28
+ external fun sqlite3_step(): Int
29
+
30
+ external fun bindStatementParam(index: Int, param: Any): Int
31
+ external fun getColumnNames(): ColumnNames
32
+ external fun getColumnValues(): ColumnValues
33
+
34
+ // endregion
35
+
36
+ // region internals
37
+
38
+ private external fun initHybrid(): HybridData
39
+
40
+ // endregion
41
+ }
@@ -1,15 +1,26 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
1
3
  package expo.modules.sqlite
2
4
 
5
+ import expo.modules.core.interfaces.DoNotStrip
3
6
  import expo.modules.kotlin.exception.CodedException
4
7
 
5
- class OpenDatabaseException(name: String) :
6
- CodedException("Unable to delete database '$name' that is currently open. Close it prior to deletion.")
8
+ internal class OpenDatabaseException(name: String) :
9
+ CodedException("Could not open database - name[$name]")
7
10
 
8
- class DatabaseNotFoundException(name: String) :
11
+ internal class DatabaseNotFoundException(name: String) :
9
12
  CodedException("Database '$name' not found")
10
13
 
11
- class DeleteDatabaseException(name: String) :
14
+ internal class DeleteDatabaseException(name: String) :
15
+ CodedException("Unable to delete database '$name' that is currently open. Close it prior to deletion.")
16
+
17
+ internal class DeleteDatabaseFileException(name: String) :
12
18
  CodedException("Unable to delete the database file for '$name' database")
13
19
 
14
- class SQLiteException(message: String?, cause: Throwable?) :
15
- CodedException(SQLiteModule::class.java.simpleName, message, cause)
20
+ @DoNotStrip
21
+ internal class SQLiteErrorException(message: String) :
22
+ CodedException("ERR_INTERNAL_SQLITE_ERROR", message, null)
23
+
24
+ @DoNotStrip
25
+ internal class InvalidConvertibleException(message: String) :
26
+ CodedException(message)
@@ -1,19 +1,32 @@
1
+ // Copyright 2015-present 650 Industries. All rights reserved.
2
+
1
3
  package expo.modules.sqlite
2
4
 
3
5
  import expo.modules.kotlin.records.Field
4
6
  import expo.modules.kotlin.records.Record
5
7
  import expo.modules.kotlin.types.Enumerable
6
8
 
7
- data class Query(
9
+ internal data class Query(
8
10
  @Field
9
11
  val sql: String,
10
12
  @Field
11
13
  val args: List<Any?>
12
14
  ) : Record
13
15
 
14
- enum class SqlAction(val value: String) : Enumerable {
16
+ internal enum class SQLAction(val value: String) : Enumerable {
15
17
  INSERT("insert"),
16
18
  UPDATE("update"),
17
19
  DELETE("delete"),
18
- UNKNOWN("unknown")
20
+ UNKNOWN("unknown");
21
+
22
+ companion object {
23
+ fun fromCode(value: Int): SQLAction {
24
+ return when (value) {
25
+ 9 -> DELETE
26
+ 18 -> INSERT
27
+ 23 -> UPDATE
28
+ else -> UNKNOWN
29
+ }
30
+ }
31
+ }
19
32
  }
@@ -3,16 +3,12 @@ package expo.modules.sqlite
3
3
  import com.facebook.jni.HybridData
4
4
  import expo.modules.core.interfaces.DoNotStrip
5
5
 
6
- private typealias UpdateListener = (tableName: String, operationType: Int, rowID: Long) -> Unit
7
-
8
6
  @Suppress("KotlinJniMissingFunction")
9
7
  @DoNotStrip
10
8
  class SQLite3Wrapper private constructor() {
11
9
  @DoNotStrip
12
10
  private val mHybridData: HybridData
13
11
 
14
- private var mUpdateListener: UpdateListener? = null
15
-
16
12
  init {
17
13
  mHybridData = initHybrid()
18
14
  }
@@ -22,43 +18,17 @@ class SQLite3Wrapper private constructor() {
22
18
  */
23
19
  external fun executeSql(sql: String, args: List<Any?>, readOnly: Boolean): List<Any>
24
20
 
25
- /**
26
- * Enable data change notifications
27
- */
28
- fun enableUpdateHook(listener: UpdateListener) {
29
- sqlite3_update_hook(true)
30
- mUpdateListener = listener
31
- }
32
-
33
- /**
34
- * Disable data change notifications
35
- */
36
- fun disableUpdateHook() {
37
- mUpdateListener = null
38
- sqlite3_update_hook(false)
39
- }
40
-
41
21
  // region sqlite3 bindings
42
22
 
43
23
  external fun sqlite3_open(dbPath: String): Int
44
24
  external fun sqlite3_close(): Int
45
25
 
46
- external fun sqlite3_enable_load_extension(onoff: Int): Int
47
- external fun sqlite3_load_extension(libPath: String, entryProc: String): Int
48
-
49
- private external fun sqlite3_update_hook(enabled: Boolean)
50
-
51
26
  // endregion
52
27
 
53
28
  // region internals
54
29
 
55
30
  private external fun initHybrid(): HybridData
56
31
 
57
- @DoNotStrip
58
- private fun onUpdate(action: Int, dbName: String, tableName: String, rowId: Long) {
59
- mUpdateListener?.invoke(tableName, action, rowId)
60
- }
61
-
62
32
  // endregion
63
33
 
64
34
  companion object {
@@ -22,82 +22,3 @@ internal fun ensureDirExists(dir: File): File {
22
22
  }
23
23
  return dir
24
24
  }
25
-
26
- internal fun pluginResultsToPrimitiveData(results: List<SQLiteModule.SQLitePluginResult>): List<Any> {
27
- return results.map { convertPluginResultToArray(it) }
28
- }
29
-
30
- private fun convertPluginResultToArray(result: SQLiteModule.SQLitePluginResult): List<Any?> {
31
- val rowsContent = result.rows.map { row ->
32
- row.map { value ->
33
- when (value) {
34
- null -> null
35
- is String -> value
36
- is Boolean -> value
37
- else -> (value as Number).toDouble()
38
- }
39
- }
40
- }
41
-
42
- return arrayListOf(
43
- result.error?.message,
44
- result.insertId.toInt(),
45
- result.rowsAffected,
46
- result.columns,
47
- rowsContent
48
- )
49
- }
50
-
51
- private fun isPragma(str: String): Boolean {
52
- return startsWithCaseInsensitive(str, "pragma")
53
- }
54
-
55
- private fun isSelectCTE(str: String): Boolean {
56
- return startsWithCaseInsensitive(str, "with") && containsCaseInsensitive(str, "select")
57
- }
58
-
59
- private fun isPragmaReadOnly(str: String): Boolean {
60
- return isPragma(str) && !str.contains('=')
61
- }
62
-
63
- internal fun isSelect(str: String): Boolean {
64
- return startsWithCaseInsensitive(str, "select") || isSelectCTE(str) || isPragmaReadOnly(str)
65
- }
66
-
67
- internal fun isInsert(str: String): Boolean {
68
- return startsWithCaseInsensitive(str, "insert")
69
- }
70
-
71
- internal fun isUpdate(str: String): Boolean {
72
- return startsWithCaseInsensitive(str, "update")
73
- }
74
-
75
- internal fun isDelete(str: String): Boolean {
76
- return startsWithCaseInsensitive(str, "delete")
77
- }
78
-
79
- private fun startsWithCaseInsensitive(str: String, substr: String): Boolean {
80
- return str.trimStart().startsWith(substr, true)
81
- }
82
-
83
- private fun containsCaseInsensitive(str: String, substr: String): Boolean {
84
- return str.trimStart().contains(substr, true)
85
- }
86
-
87
- internal fun convertParamsToStringArray(paramArrayArg: List<Any?>): Array<String?> {
88
- return paramArrayArg.map { param ->
89
- when (param) {
90
- is String -> unescapeBlob(param)
91
- is Boolean -> if (param) "1" else "0"
92
- is Double -> param.toString()
93
- null -> null
94
- else -> throw ClassCastException("Could not find proper SQLite data type for argument: $")
95
- }
96
- }.toTypedArray()
97
- }
98
-
99
- private fun unescapeBlob(str: String): String {
100
- return str.replace("\u0001\u0001", "\u0000")
101
- .replace("\u0001\u0002", "\u0001")
102
- .replace("\u0002\u0002", "\u0002")
103
- }
@@ -2,9 +2,7 @@
2
2
  package expo.modules.sqlite
3
3
 
4
4
  import android.content.Context
5
- import android.util.Log
6
5
  import androidx.collection.ArrayMap
7
- import androidx.core.os.bundleOf
8
6
  import expo.modules.kotlin.exception.Exceptions
9
7
  import expo.modules.kotlin.modules.Module
10
8
  import expo.modules.kotlin.modules.ModuleDefinition
@@ -14,7 +12,6 @@ import java.util.*
14
12
 
15
13
  class SQLiteModule : Module() {
16
14
  private val cachedDatabase = ArrayMap<String, SQLite3Wrapper>()
17
- private var hasListeners = false
18
15
 
19
16
  private val context: Context
20
17
  get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
@@ -22,8 +19,6 @@ class SQLiteModule : Module() {
22
19
  override fun definition() = ModuleDefinition {
23
20
  Name("ExpoSQLite")
24
21
 
25
- Events("onDatabaseChange")
26
-
27
22
  AsyncFunction("exec") { dbName: String, queries: List<Query>, readOnly: Boolean ->
28
23
  return@AsyncFunction execute(dbName, queries, readOnly)
29
24
  }
@@ -46,28 +41,19 @@ class SQLiteModule : Module() {
46
41
 
47
42
  AsyncFunction("deleteAsync") { dbName: String ->
48
43
  if (cachedDatabase[dbName] != null) {
49
- throw OpenDatabaseException(dbName)
44
+ throw DeleteDatabaseException(dbName)
50
45
  }
51
46
  val dbFile = File(pathForDatabaseName(dbName))
52
47
  if (!dbFile.exists()) {
53
48
  throw DatabaseNotFoundException(dbName)
54
49
  }
55
50
  if (!dbFile.delete()) {
56
- throw DeleteDatabaseException(dbName)
51
+ throw DeleteDatabaseFileException(dbName)
57
52
  }
58
53
  }
59
54
 
60
- OnStartObserving {
61
- hasListeners = true
62
- }
63
-
64
- OnStopObserving {
65
- hasListeners = false
66
- }
67
-
68
55
  OnDestroy {
69
56
  cachedDatabase.values.forEach {
70
- it.executeSql("SELECT crsql_finalize()", emptyList(), false)
71
57
  it.sqlite3_close()
72
58
  }
73
59
  }
@@ -96,50 +82,15 @@ class SQLiteModule : Module() {
96
82
 
97
83
  cachedDatabase.remove(dbName)
98
84
  val db = SQLite3Wrapper.open(path) ?: return null
99
- loadExtensions(db)
100
- addUpdateListener(db)
101
85
  cachedDatabase[dbName] = db
102
86
  return db
103
87
  }
104
88
 
105
- private fun loadExtensions(db: SQLite3Wrapper) {
106
- var errCode = db.sqlite3_enable_load_extension(1)
107
- if (errCode != SQLite3Wrapper.SQLITE_OK) {
108
- Log.e(TAG, "Failed to enable sqlite3 extensions - errCode[$errCode]")
109
- return
110
- }
111
- errCode = db.sqlite3_load_extension("libcrsqlite", "sqlite3_crsqlite_init")
112
- if (errCode != SQLite3Wrapper.SQLITE_OK) {
113
- Log.e(TAG, "Failed to load crsqlite extension - errCode[$errCode]")
114
- }
115
- }
116
-
117
89
  private fun execute(dbName: String, queries: List<Query>, readOnly: Boolean): List<Any> {
118
90
  val db = openDatabase(dbName) ?: throw OpenDatabaseException(dbName)
119
91
  return queries.map { db.executeSql(it.sql, it.args, readOnly) }
120
92
  }
121
93
 
122
- private fun addUpdateListener(db: SQLite3Wrapper) {
123
- db.enableUpdateHook { tableName: String, operationType: Int, rowID: Long ->
124
- if (!hasListeners) {
125
- return@enableUpdateHook
126
- }
127
- sendEvent(
128
- "onDatabaseChange",
129
- bundleOf(
130
- "tableName" to tableName,
131
- "rowId" to rowID,
132
- "typeId" to when (operationType) {
133
- 9 -> SqlAction.DELETE.value
134
- 18 -> SqlAction.INSERT.value
135
- 23 -> SqlAction.UPDATE.value
136
- else -> SqlAction.UNKNOWN.value
137
- }
138
- )
139
- )
140
- }
141
- }
142
-
143
94
  internal class SQLitePluginResult(
144
95
  val rows: Array<Array<Any?>>,
145
96
  val columns: Array<String?>,
@@ -147,8 +98,4 @@ class SQLiteModule : Module() {
147
98
  val insertId: Long,
148
99
  val error: Throwable?
149
100
  )
150
-
151
- companion object {
152
- private val TAG = SQLiteModule::class.java.simpleName
153
- }
154
101
  }