expo-sqlite 11.1.1 → 11.3.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.
- package/CHANGELOG.md +12 -0
- package/README.md +10 -3
- package/android/build.gradle +8 -10
- package/android/src/main/AndroidManifest.xml +1 -3
- package/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt +1 -1
- package/build/SQLite.js +5 -5
- package/build/SQLite.js.map +1 -1
- package/expo-module.config.json +7 -0
- package/ios/Exceptions.swift +53 -0
- package/ios/{EXSQLite.podspec → ExpoSQLite.podspec} +3 -3
- package/ios/SQLiteModule.swift +211 -0
- package/package.json +2 -2
- package/src/SQLite.ts +5 -5
- package/ios/EXSQLite/EXSQLite.h +0 -8
- package/ios/EXSQLite/EXSQLite.m +0 -270
- package/unimodule.json +0 -4
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 11.3.0 — 2023-06-21
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- Fixed Android build warnings for Gradle version 8. ([#22537](https://github.com/expo/expo/pull/22537), [#22609](https://github.com/expo/expo/pull/22609) by [@kudo](https://github.com/kudo))
|
|
18
|
+
|
|
19
|
+
## 11.2.0 — 2023-05-08
|
|
20
|
+
|
|
21
|
+
### 🎉 New features
|
|
22
|
+
|
|
23
|
+
- Migrated to Expo Modules API. ([#21721](https://github.com/expo/expo/pull/21721) by [@alanjhughes](https://github.com/alanjhughes))
|
|
24
|
+
|
|
13
25
|
## 11.1.1 — 2023-02-09
|
|
14
26
|
|
|
15
27
|
_This version does not introduce any user-facing changes._
|
package/README.md
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
<p>
|
|
2
|
+
<a href="https://docs.expo.dev/versions/latest/sdk/sqlite/">
|
|
3
|
+
<img
|
|
4
|
+
src="../../.github/resources/expo-sqlite.svg"
|
|
5
|
+
alt="expo-sqlite"
|
|
6
|
+
height="64" />
|
|
7
|
+
</a>
|
|
8
|
+
</p>
|
|
2
9
|
|
|
3
10
|
Provides access to a database that can be queried through a WebSQL-like API (https://www.w3.org/TR/webdatabase/). The database is persisted across restarts of your app.
|
|
4
11
|
|
|
@@ -9,7 +16,7 @@ Provides access to a database that can be queried through a WebSQL-like API (htt
|
|
|
9
16
|
|
|
10
17
|
# Installation in managed Expo projects
|
|
11
18
|
|
|
12
|
-
For [managed](https://docs.expo.dev/
|
|
19
|
+
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/sqlite/).
|
|
13
20
|
|
|
14
21
|
# Installation in bare React Native projects
|
|
15
22
|
|
|
@@ -18,7 +25,7 @@ For bare React Native projects, you must ensure that you have [installed and con
|
|
|
18
25
|
### Add the package to your npm dependencies
|
|
19
26
|
|
|
20
27
|
```
|
|
21
|
-
expo install expo-sqlite
|
|
28
|
+
npx expo install expo-sqlite
|
|
22
29
|
```
|
|
23
30
|
|
|
24
31
|
### Configure for iOS
|
package/android/build.gradle
CHANGED
|
@@ -3,7 +3,7 @@ apply plugin: 'kotlin-android'
|
|
|
3
3
|
apply plugin: 'maven-publish'
|
|
4
4
|
|
|
5
5
|
group = 'host.exp.exponent'
|
|
6
|
-
version = '11.
|
|
6
|
+
version = '11.3.0'
|
|
7
7
|
|
|
8
8
|
buildscript {
|
|
9
9
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
@@ -35,19 +35,11 @@ buildscript {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// Creating sources with comments
|
|
39
|
-
task androidSourcesJar(type: Jar) {
|
|
40
|
-
classifier = 'sources'
|
|
41
|
-
from android.sourceSets.main.java.srcDirs
|
|
42
|
-
}
|
|
43
|
-
|
|
44
38
|
afterEvaluate {
|
|
45
39
|
publishing {
|
|
46
40
|
publications {
|
|
47
41
|
release(MavenPublication) {
|
|
48
42
|
from components.release
|
|
49
|
-
// Add additional sourcesJar to artifacts
|
|
50
|
-
artifact(androidSourcesJar)
|
|
51
43
|
}
|
|
52
44
|
}
|
|
53
45
|
repositories {
|
|
@@ -70,15 +62,21 @@ android {
|
|
|
70
62
|
jvmTarget = JavaVersion.VERSION_11.majorVersion
|
|
71
63
|
}
|
|
72
64
|
|
|
65
|
+
namespace "expo.modules.sqlite"
|
|
73
66
|
defaultConfig {
|
|
74
67
|
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
75
68
|
targetSdkVersion safeExtGet("targetSdkVersion", 33)
|
|
76
69
|
versionCode 18
|
|
77
|
-
versionName "11.
|
|
70
|
+
versionName "11.3.0"
|
|
78
71
|
}
|
|
79
72
|
lintOptions {
|
|
80
73
|
abortOnError false
|
|
81
74
|
}
|
|
75
|
+
publishing {
|
|
76
|
+
singleVariant("release") {
|
|
77
|
+
withSourcesJar()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
82
80
|
}
|
|
83
81
|
|
|
84
82
|
dependencies {
|
|
@@ -19,7 +19,7 @@ private val DATABASES: MutableMap<String, SQLiteDatabase?> = HashMap()
|
|
|
19
19
|
|
|
20
20
|
class SQLiteModule(private val mContext: Context) : ExportedModule(mContext) {
|
|
21
21
|
override fun getName(): String {
|
|
22
|
-
return "
|
|
22
|
+
return "ExpoSQLite"
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
@ExpoMethod
|
package/build/SQLite.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import './polyfillNextTick';
|
|
2
2
|
import customOpenDatabase from '@expo/websql/custom';
|
|
3
|
-
import {
|
|
3
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
4
4
|
import { Platform } from 'react-native';
|
|
5
|
-
const
|
|
5
|
+
const ExpoSQLite = requireNativeModule('ExpoSQLite');
|
|
6
6
|
function zipObject(keys, values) {
|
|
7
7
|
const result = {};
|
|
8
8
|
for (let i = 0; i < keys.length; i++) {
|
|
@@ -20,7 +20,7 @@ class SQLiteDatabase {
|
|
|
20
20
|
if (this._closed) {
|
|
21
21
|
throw new Error(`The SQLite database is closed`);
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
ExpoSQLite.exec(this._name, queries.map(_serializeQuery), readOnly).then((nativeResultSets) => {
|
|
24
24
|
callback(null, nativeResultSets.map(_deserializeResultSet));
|
|
25
25
|
}, (error) => {
|
|
26
26
|
// TODO: make the native API consistently reject with an error, not a string or other type
|
|
@@ -29,13 +29,13 @@ class SQLiteDatabase {
|
|
|
29
29
|
}
|
|
30
30
|
close() {
|
|
31
31
|
this._closed = true;
|
|
32
|
-
return
|
|
32
|
+
return ExpoSQLite.close(this._name);
|
|
33
33
|
}
|
|
34
34
|
deleteAsync() {
|
|
35
35
|
if (!this._closed) {
|
|
36
36
|
throw new Error(`Unable to delete '${this._name}' database that is currently open. Close it prior to deletion.`);
|
|
37
37
|
}
|
|
38
|
-
return
|
|
38
|
+
return ExpoSQLite.deleteAsync(this._name);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
function _serializeQuery(query) {
|
package/build/SQLite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SQLite.js","sourceRoot":"","sources":["../src/SQLite.ts"],"names":[],"mappings":"AAAA,OAAO,oBAAoB,CAAC;AAE5B,OAAO,kBAAkB,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,
|
|
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<void> {\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<T>(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"]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
internal class DatabaseException: Exception {
|
|
4
|
+
override var code: String {
|
|
5
|
+
"E_SQLITE_OPEN_DATABASE"
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
override var reason: String {
|
|
9
|
+
"Could not open database"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
internal class DeleteDatabaseException: GenericException<String> {
|
|
14
|
+
override var code: String {
|
|
15
|
+
"E_SQLITE_DELETE_DATABASE"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override var reason: String {
|
|
19
|
+
"Unable to delete database \(param) that is currently open. Close it prior to deletion"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
internal class DatabaseNotFoundException: GenericException<String> {
|
|
24
|
+
override var code: String {
|
|
25
|
+
"E_SQLITE_DELETE_DATABASE"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
override var reason: String {
|
|
29
|
+
"Database \(param) not found"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
internal class DeleteDatabaseFileException: GenericException<String> {
|
|
34
|
+
override var code: String {
|
|
35
|
+
"E_SQLITE_DELETE_DATABASE"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override var reason: String {
|
|
39
|
+
"Unable to delete the database file for \(param) database"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
internal class InvalidSqlException: Exception {
|
|
44
|
+
override var reason: String {
|
|
45
|
+
"sql argument must be a string"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
internal class InvalidArgumentsException: Exception {
|
|
50
|
+
override var reason: String {
|
|
51
|
+
"args must be an array"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -3,7 +3,7 @@ require 'json'
|
|
|
3
3
|
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
|
|
4
4
|
|
|
5
5
|
Pod::Spec.new do |s|
|
|
6
|
-
s.name = '
|
|
6
|
+
s.name = 'ExpoSQLite'
|
|
7
7
|
s.version = package['version']
|
|
8
8
|
s.summary = package['description']
|
|
9
9
|
s.description = package['description']
|
|
@@ -17,9 +17,9 @@ Pod::Spec.new do |s|
|
|
|
17
17
|
s.dependency 'ExpoModulesCore'
|
|
18
18
|
|
|
19
19
|
if !$ExpoUseSources&.include?(package['name']) && ENV['EXPO_USE_SOURCE'].to_i == 0 && File.exist?("#{s.name}.xcframework") && Gem::Version.new(Pod::VERSION) >= Gem::Version.new('1.10.0')
|
|
20
|
-
s.source_files = "
|
|
20
|
+
s.source_files = "**/*.h"
|
|
21
21
|
s.vendored_frameworks = "#{s.name}.xcframework"
|
|
22
22
|
else
|
|
23
|
-
s.source_files = "
|
|
23
|
+
s.source_files = "**/*.{h,m,swift}"
|
|
24
24
|
end
|
|
25
25
|
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import SQLite3
|
|
3
|
+
|
|
4
|
+
public final class SQLiteModule: Module {
|
|
5
|
+
private var cachedDatabases = [String: OpaquePointer]()
|
|
6
|
+
|
|
7
|
+
public func definition() -> ModuleDefinition {
|
|
8
|
+
Name("ExpoSQLite")
|
|
9
|
+
|
|
10
|
+
AsyncFunction("exec") { (dbName: String, queries: [[Any]], readOnly: Bool) -> [Any?] in
|
|
11
|
+
guard let db = openDatabase(dbName: dbName) else {
|
|
12
|
+
throw DatabaseException()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let results = try queries.map { query in
|
|
16
|
+
guard let sql = query[0] as? String else {
|
|
17
|
+
throw InvalidSqlException()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
guard let args = query[1] as? [Any] else {
|
|
21
|
+
throw InvalidArgumentsException()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return executeSql(sql: sql, with: args, for: db, readOnly: readOnly)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return results
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
AsyncFunction("close") { (dbName: String) in
|
|
31
|
+
cachedDatabases.removeValue(forKey: dbName)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
AsyncFunction("deleteAsync") { (dbName: String) in
|
|
35
|
+
if cachedDatabases[dbName] != nil {
|
|
36
|
+
throw DeleteDatabaseException(dbName)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
guard let path = self.pathForDatabaseName(name: dbName) else {
|
|
40
|
+
throw Exceptions.FileSystemModuleNotFound()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if !FileManager.default.fileExists(atPath: path.absoluteString) {
|
|
44
|
+
throw DatabaseNotFoundException(dbName)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
do {
|
|
48
|
+
try FileManager.default.removeItem(atPath: path.absoluteString)
|
|
49
|
+
} catch {
|
|
50
|
+
throw DeleteDatabaseFileException(dbName)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
OnDestroy {
|
|
55
|
+
cachedDatabases.values.forEach {
|
|
56
|
+
sqlite3_close($0)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private func pathForDatabaseName(name: String) -> URL? {
|
|
62
|
+
guard let fileSystem = appContext?.fileSystem else {
|
|
63
|
+
return nil
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
var directory = URL(string: fileSystem.documentDirectory)?.appendingPathComponent("SQLite")
|
|
67
|
+
fileSystem.ensureDirExists(withPath: directory?.absoluteString)
|
|
68
|
+
|
|
69
|
+
return directory?.appendingPathComponent(name)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private func openDatabase(dbName: String) -> OpaquePointer? {
|
|
73
|
+
var db: OpaquePointer?
|
|
74
|
+
guard let path = try pathForDatabaseName(name: dbName) else {
|
|
75
|
+
return nil
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let fileExists = FileManager.default.fileExists(atPath: path.absoluteString)
|
|
79
|
+
|
|
80
|
+
if fileExists {
|
|
81
|
+
db = cachedDatabases[dbName]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if db == nil {
|
|
85
|
+
cachedDatabases.removeValue(forKey: dbName)
|
|
86
|
+
if sqlite3_open(path.absoluteString, &db) != SQLITE_OK {
|
|
87
|
+
return nil
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
cachedDatabases[dbName] = db
|
|
91
|
+
}
|
|
92
|
+
return db
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
private func executeSql(sql: String, with args: [Any], for db: OpaquePointer, readOnly: Bool) -> [Any?] {
|
|
96
|
+
var resultRows = [Any]()
|
|
97
|
+
var statement: OpaquePointer?
|
|
98
|
+
var rowsAffected: Int32 = 0
|
|
99
|
+
var insertId: Int64 = 0
|
|
100
|
+
var error: String?
|
|
101
|
+
|
|
102
|
+
if sqlite3_prepare_v2(db, sql, -1, &statement, nil) != SQLITE_OK {
|
|
103
|
+
return [convertSqlLiteErrorToString(db: db)]
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let queryIsReadOnly = sqlite3_stmt_readonly(statement) > 0
|
|
107
|
+
|
|
108
|
+
if readOnly && !queryIsReadOnly {
|
|
109
|
+
return ["could not prepare \(sql)"]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (index, arg) in args.enumerated() {
|
|
113
|
+
guard let obj = arg as? NSObject else { continue }
|
|
114
|
+
bindStatement(statement: statement, with: obj, at: Int32(index + 1))
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
var columnCount: Int32 = 0
|
|
118
|
+
var columnNames = [String]()
|
|
119
|
+
var columnType: Int32
|
|
120
|
+
var fetchedColumns = false
|
|
121
|
+
var value: Any?
|
|
122
|
+
var hasMore = true
|
|
123
|
+
|
|
124
|
+
while hasMore {
|
|
125
|
+
let result = sqlite3_step(statement)
|
|
126
|
+
|
|
127
|
+
switch result {
|
|
128
|
+
case SQLITE_ROW:
|
|
129
|
+
if !fetchedColumns {
|
|
130
|
+
columnCount = sqlite3_column_count(statement)
|
|
131
|
+
|
|
132
|
+
for i in 0..<Int(columnCount) {
|
|
133
|
+
let columnName = NSString(format: "%s", sqlite3_column_name(statement, Int32(i))) as String
|
|
134
|
+
columnNames.append(columnName)
|
|
135
|
+
}
|
|
136
|
+
fetchedColumns = true
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
var entry = [Any]()
|
|
140
|
+
|
|
141
|
+
for i in 0..<Int(columnCount) {
|
|
142
|
+
columnType = sqlite3_column_type(statement, Int32(i))
|
|
143
|
+
value = getSqlValue(for: columnType, with: statement, index: Int32(i))
|
|
144
|
+
entry.append(value)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
resultRows.append(entry)
|
|
148
|
+
case SQLITE_DONE:
|
|
149
|
+
hasMore = false
|
|
150
|
+
default:
|
|
151
|
+
error = convertSqlLiteErrorToString(db: db)
|
|
152
|
+
hasMore = false
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if !queryIsReadOnly {
|
|
157
|
+
rowsAffected = sqlite3_changes(db)
|
|
158
|
+
if rowsAffected > 0 {
|
|
159
|
+
insertId = sqlite3_last_insert_rowid(db)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
sqlite3_finalize(statement)
|
|
164
|
+
|
|
165
|
+
if error != nil {
|
|
166
|
+
return [error]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return [nil, insertId, rowsAffected, columnNames, resultRows]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private func bindStatement(statement: OpaquePointer?, with arg: NSObject, at index: Int32) {
|
|
173
|
+
if arg == NSNull() {
|
|
174
|
+
sqlite3_bind_null(statement, index)
|
|
175
|
+
} else if arg is Double {
|
|
176
|
+
sqlite3_bind_double(statement, index, arg as? Double ?? 0.0)
|
|
177
|
+
} else {
|
|
178
|
+
var stringArg: NSString
|
|
179
|
+
|
|
180
|
+
if arg is NSString {
|
|
181
|
+
stringArg = NSString(format: "%@", arg)
|
|
182
|
+
} else {
|
|
183
|
+
stringArg = arg.description as NSString
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let SQLITE_TRANSIENT = unsafeBitCast(OpaquePointer(bitPattern: -1), to: sqlite3_destructor_type.self)
|
|
187
|
+
|
|
188
|
+
let data = stringArg.data(using: NSUTF8StringEncoding)
|
|
189
|
+
sqlite3_bind_text(statement, index, stringArg.utf8String, Int32(data?.count ?? 0), SQLITE_TRANSIENT)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private func getSqlValue(for columnType: Int32, with statement: OpaquePointer?, index: Int32) -> Any? {
|
|
194
|
+
switch columnType {
|
|
195
|
+
case SQLITE_INTEGER:
|
|
196
|
+
return sqlite3_column_int64(statement, index)
|
|
197
|
+
case SQLITE_FLOAT:
|
|
198
|
+
return sqlite3_column_double(statement, index)
|
|
199
|
+
case SQLITE_BLOB, SQLITE_TEXT:
|
|
200
|
+
return NSString(bytes: sqlite3_column_text(statement, index), length: Int(sqlite3_column_bytes(statement, index)), encoding: NSUTF8StringEncoding)
|
|
201
|
+
default:
|
|
202
|
+
return nil
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private func convertSqlLiteErrorToString(db: OpaquePointer?) -> String {
|
|
207
|
+
let code = sqlite3_errcode(db)
|
|
208
|
+
let message = NSString(utf8String: sqlite3_errmsg(db)) ?? ""
|
|
209
|
+
return NSString(format: "Error code %i: %@", code, message) as String
|
|
210
|
+
}
|
|
211
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-sqlite",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.3.0",
|
|
4
4
|
"description": "Provides access to a database that can be queried through a WebSQL-like API (https://www.w3.org/TR/webdatabase/). The database is persisted across restarts of your app.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"expo": "*"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "fa5ecca8251986b9f197cc14074eec0ab6dfb6db"
|
|
48
48
|
}
|
package/src/SQLite.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import './polyfillNextTick';
|
|
2
2
|
|
|
3
3
|
import customOpenDatabase from '@expo/websql/custom';
|
|
4
|
-
import {
|
|
4
|
+
import { requireNativeModule } from 'expo-modules-core';
|
|
5
5
|
import { Platform } from 'react-native';
|
|
6
6
|
|
|
7
7
|
import { Query, ResultSet, ResultSetError, SQLiteCallback, WebSQLDatabase } from './SQLite.types';
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const ExpoSQLite = requireNativeModule('ExpoSQLite');
|
|
10
10
|
|
|
11
11
|
function zipObject(keys: string[], values: any[]) {
|
|
12
12
|
const result = {};
|
|
@@ -29,7 +29,7 @@ class SQLiteDatabase {
|
|
|
29
29
|
throw new Error(`The SQLite database is closed`);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
ExpoSQLite.exec(this._name, queries.map(_serializeQuery), readOnly).then(
|
|
33
33
|
(nativeResultSets) => {
|
|
34
34
|
callback(null, nativeResultSets.map(_deserializeResultSet));
|
|
35
35
|
},
|
|
@@ -42,7 +42,7 @@ class SQLiteDatabase {
|
|
|
42
42
|
|
|
43
43
|
close() {
|
|
44
44
|
this._closed = true;
|
|
45
|
-
return
|
|
45
|
+
return ExpoSQLite.close(this._name);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
deleteAsync(): Promise<void> {
|
|
@@ -52,7 +52,7 @@ class SQLiteDatabase {
|
|
|
52
52
|
);
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
return
|
|
55
|
+
return ExpoSQLite.deleteAsync(this._name);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
package/ios/EXSQLite/EXSQLite.h
DELETED
package/ios/EXSQLite/EXSQLite.m
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
-
|
|
3
|
-
#import <EXSQLite/EXSQLite.h>
|
|
4
|
-
|
|
5
|
-
#import <ExpoModulesCore/EXFileSystemInterface.h>
|
|
6
|
-
|
|
7
|
-
#import <sqlite3.h>
|
|
8
|
-
|
|
9
|
-
@interface EXSQLite ()
|
|
10
|
-
|
|
11
|
-
@property (nonatomic, copy) NSMutableDictionary *cachedDatabases;
|
|
12
|
-
@property (nonatomic, weak) EXModuleRegistry *moduleRegistry;
|
|
13
|
-
|
|
14
|
-
@end
|
|
15
|
-
|
|
16
|
-
@implementation EXSQLite
|
|
17
|
-
|
|
18
|
-
@synthesize cachedDatabases;
|
|
19
|
-
|
|
20
|
-
- (dispatch_queue_t)methodQueue
|
|
21
|
-
{
|
|
22
|
-
return dispatch_get_main_queue();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
EX_EXPORT_MODULE(ExponentSQLite);
|
|
26
|
-
|
|
27
|
-
- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
|
|
28
|
-
{
|
|
29
|
-
_moduleRegistry = moduleRegistry;
|
|
30
|
-
cachedDatabases = [NSMutableDictionary dictionary];
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
- (NSString *)pathForDatabaseName:(NSString *)name
|
|
34
|
-
{
|
|
35
|
-
id<EXFileSystemInterface> fileSystem = [_moduleRegistry getModuleImplementingProtocol:@protocol(EXFileSystemInterface)];
|
|
36
|
-
if (!fileSystem) {
|
|
37
|
-
EXLogError(@"No FileSystem module.");
|
|
38
|
-
return nil;
|
|
39
|
-
}
|
|
40
|
-
NSString *directory = [fileSystem.documentDirectory stringByAppendingPathComponent:@"SQLite"];
|
|
41
|
-
[fileSystem ensureDirExistsWithPath:directory];
|
|
42
|
-
return [directory stringByAppendingPathComponent:name];
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
- (NSValue *)openDatabase:(NSString *)dbName
|
|
46
|
-
{
|
|
47
|
-
NSValue *cachedDB = nil;
|
|
48
|
-
NSString *path = [self pathForDatabaseName:dbName];
|
|
49
|
-
if (!path) {
|
|
50
|
-
return nil;
|
|
51
|
-
}
|
|
52
|
-
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
|
53
|
-
cachedDB = [cachedDatabases objectForKey:dbName];
|
|
54
|
-
}
|
|
55
|
-
if (cachedDB == nil) {
|
|
56
|
-
[cachedDatabases removeObjectForKey:dbName];
|
|
57
|
-
sqlite3 *db;
|
|
58
|
-
if (sqlite3_open([path UTF8String], &db) != SQLITE_OK) {
|
|
59
|
-
return nil;
|
|
60
|
-
};
|
|
61
|
-
cachedDB = [NSValue valueWithPointer:db];
|
|
62
|
-
[cachedDatabases setObject:cachedDB forKey:dbName];
|
|
63
|
-
}
|
|
64
|
-
return cachedDB;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
EX_EXPORT_METHOD_AS(exec,
|
|
68
|
-
exec:(NSString *)dbName
|
|
69
|
-
queries:(NSArray *)sqlQueries
|
|
70
|
-
readOnly:(BOOL)readOnly
|
|
71
|
-
resolver:(EXPromiseResolveBlock)resolve
|
|
72
|
-
rejecter:(EXPromiseRejectBlock)reject)
|
|
73
|
-
{
|
|
74
|
-
@synchronized(self) {
|
|
75
|
-
NSValue *databasePointer = [self openDatabase:dbName];
|
|
76
|
-
if (!databasePointer) {
|
|
77
|
-
reject(@"E_SQLITE_OPEN_DATABASE", @"Could not open database.", nil);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
sqlite3 *db = [databasePointer pointerValue];
|
|
82
|
-
NSMutableArray *sqlResults = [NSMutableArray arrayWithCapacity:sqlQueries.count];
|
|
83
|
-
for (NSArray *sqlQueryObject in sqlQueries) {
|
|
84
|
-
NSString *sql = [sqlQueryObject objectAtIndex:0];
|
|
85
|
-
NSArray *sqlArgs = [sqlQueryObject objectAtIndex:1];
|
|
86
|
-
[sqlResults addObject:[self executeSql:sql withSqlArgs:sqlArgs withDb:db withReadOnly:readOnly]];
|
|
87
|
-
}
|
|
88
|
-
resolve(sqlResults);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
EX_EXPORT_METHOD_AS(close,
|
|
93
|
-
close:(NSString *)dbName
|
|
94
|
-
resolver:(EXPromiseResolveBlock)resolve
|
|
95
|
-
rejecter:(EXPromiseRejectBlock)reject)
|
|
96
|
-
{
|
|
97
|
-
@synchronized(self) {
|
|
98
|
-
[cachedDatabases removeObjectForKey:dbName];
|
|
99
|
-
resolve(nil);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
EX_EXPORT_METHOD_AS(deleteAsync,
|
|
104
|
-
deleteDbName:(NSString *)dbName
|
|
105
|
-
resolver:(EXPromiseResolveBlock)resolve
|
|
106
|
-
rejecter:(EXPromiseRejectBlock)reject)
|
|
107
|
-
{
|
|
108
|
-
NSString *errorCode = @"E_SQLITE_DELETE_DATABASE";
|
|
109
|
-
|
|
110
|
-
@synchronized(self) {
|
|
111
|
-
if ([cachedDatabases objectForKey:dbName]) {
|
|
112
|
-
reject(errorCode, [NSString stringWithFormat:@"Unable to delete database '%@' that is currently open. Close it prior to deletion.", dbName], nil);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
NSString *path = [self pathForDatabaseName:dbName];
|
|
118
|
-
if (!path) {
|
|
119
|
-
reject(errorCode, @"No FileSystem module.", nil);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
|
|
123
|
-
reject(errorCode, [NSString stringWithFormat:@"Database '%@' not found", dbName], nil);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
NSError *error;
|
|
127
|
-
if (![[NSFileManager defaultManager] removeItemAtPath:path error:&error]) {
|
|
128
|
-
reject(errorCode, [NSString stringWithFormat:@"Unable to delete the database file for '%@' database", dbName], error);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
resolve(nil);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
- (id)getSqlValueForColumnType:(int)columnType withStatement:(sqlite3_stmt*)statement withIndex:(int)i
|
|
135
|
-
{
|
|
136
|
-
switch (columnType) {
|
|
137
|
-
case SQLITE_INTEGER:
|
|
138
|
-
return @(sqlite3_column_int64(statement, i));
|
|
139
|
-
case SQLITE_FLOAT:
|
|
140
|
-
return @(sqlite3_column_double(statement, i));
|
|
141
|
-
case SQLITE_BLOB:
|
|
142
|
-
case SQLITE_TEXT:
|
|
143
|
-
return [[NSString alloc] initWithBytes:(char *)sqlite3_column_text(statement, i)
|
|
144
|
-
length:sqlite3_column_bytes(statement, i)
|
|
145
|
-
encoding:NSUTF8StringEncoding];
|
|
146
|
-
}
|
|
147
|
-
return [NSNull null];
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
- (NSArray *)executeSql:(NSString*)sql withSqlArgs:(NSArray*)sqlArgs
|
|
151
|
-
withDb:(sqlite3*)db withReadOnly:(BOOL)readOnly
|
|
152
|
-
{
|
|
153
|
-
NSString *error = nil;
|
|
154
|
-
sqlite3_stmt *statement;
|
|
155
|
-
NSMutableArray *resultRows = [NSMutableArray arrayWithCapacity:0];
|
|
156
|
-
NSMutableArray *entry;
|
|
157
|
-
long long insertId = 0;
|
|
158
|
-
int rowsAffected = 0;
|
|
159
|
-
int i;
|
|
160
|
-
|
|
161
|
-
// compile the statement, throw an error if necessary
|
|
162
|
-
if (sqlite3_prepare_v2(db, [sql UTF8String], -1, &statement, NULL) != SQLITE_OK) {
|
|
163
|
-
error = [EXSQLite convertSQLiteErrorToString:db];
|
|
164
|
-
return @[error];
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
bool queryIsReadOnly = sqlite3_stmt_readonly(statement);
|
|
168
|
-
if (readOnly && !queryIsReadOnly) {
|
|
169
|
-
error = [NSString stringWithFormat:@"could not prepare %@", sql];
|
|
170
|
-
return @[error];
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// bind any arguments
|
|
174
|
-
if (sqlArgs != nil) {
|
|
175
|
-
for (i = 0; i < sqlArgs.count; i++) {
|
|
176
|
-
[self bindStatement:statement withArg:[sqlArgs objectAtIndex:i] atIndex:(i + 1)];
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// iterate through sql results
|
|
181
|
-
int columnCount = 0;
|
|
182
|
-
NSMutableArray *columnNames = [NSMutableArray arrayWithCapacity:0];
|
|
183
|
-
NSString *columnName;
|
|
184
|
-
int columnType;
|
|
185
|
-
BOOL fetchedColumns = NO;
|
|
186
|
-
int result;
|
|
187
|
-
NSObject *columnValue;
|
|
188
|
-
BOOL hasMore = YES;
|
|
189
|
-
while (hasMore) {
|
|
190
|
-
result = sqlite3_step (statement);
|
|
191
|
-
switch (result) {
|
|
192
|
-
case SQLITE_ROW:
|
|
193
|
-
if (!fetchedColumns) {
|
|
194
|
-
// get all column names once at the beginning
|
|
195
|
-
columnCount = sqlite3_column_count(statement);
|
|
196
|
-
|
|
197
|
-
for (i = 0; i < columnCount; i++) {
|
|
198
|
-
columnName = [NSString stringWithFormat:@"%s", sqlite3_column_name(statement, i)];
|
|
199
|
-
[columnNames addObject:columnName];
|
|
200
|
-
}
|
|
201
|
-
fetchedColumns = YES;
|
|
202
|
-
}
|
|
203
|
-
entry = [NSMutableArray arrayWithCapacity:columnCount];
|
|
204
|
-
for (i = 0; i < columnCount; i++) {
|
|
205
|
-
columnType = sqlite3_column_type(statement, i);
|
|
206
|
-
columnValue = [self getSqlValueForColumnType:columnType withStatement:statement withIndex: i];
|
|
207
|
-
[entry addObject:columnValue];
|
|
208
|
-
}
|
|
209
|
-
[resultRows addObject:entry];
|
|
210
|
-
break;
|
|
211
|
-
case SQLITE_DONE:
|
|
212
|
-
hasMore = NO;
|
|
213
|
-
break;
|
|
214
|
-
default:
|
|
215
|
-
error = [EXSQLite convertSQLiteErrorToString:db];
|
|
216
|
-
hasMore = NO;
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (!queryIsReadOnly) {
|
|
222
|
-
rowsAffected = sqlite3_changes(db);
|
|
223
|
-
if (rowsAffected > 0) {
|
|
224
|
-
insertId = sqlite3_last_insert_rowid(db);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
sqlite3_finalize(statement);
|
|
229
|
-
|
|
230
|
-
if (error) {
|
|
231
|
-
return @[error];
|
|
232
|
-
}
|
|
233
|
-
return @[[NSNull null], @(insertId), @(rowsAffected), columnNames, resultRows];
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
- (void)bindStatement:(sqlite3_stmt *)statement withArg:(NSObject *)arg atIndex:(int)argIndex
|
|
237
|
-
{
|
|
238
|
-
if ([arg isEqual:[NSNull null]]) {
|
|
239
|
-
sqlite3_bind_null(statement, argIndex);
|
|
240
|
-
} else if ([arg isKindOfClass:[NSNumber class]]) {
|
|
241
|
-
sqlite3_bind_double(statement, argIndex, [((NSNumber *) arg) doubleValue]);
|
|
242
|
-
} else { // NSString
|
|
243
|
-
NSString *stringArg;
|
|
244
|
-
|
|
245
|
-
if ([arg isKindOfClass:[NSString class]]) {
|
|
246
|
-
stringArg = (NSString *)arg;
|
|
247
|
-
} else {
|
|
248
|
-
stringArg = [arg description]; // convert to text
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
NSData *data = [stringArg dataUsingEncoding:NSUTF8StringEncoding];
|
|
252
|
-
sqlite3_bind_text(statement, argIndex, data.bytes, (int)data.length, SQLITE_TRANSIENT);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
- (void)dealloc
|
|
257
|
-
{
|
|
258
|
-
for (NSString *key in cachedDatabases) {
|
|
259
|
-
sqlite3_close([[cachedDatabases objectForKey:key] pointerValue]);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
+ (NSString *)convertSQLiteErrorToString:(struct sqlite3 *)db
|
|
264
|
-
{
|
|
265
|
-
int code = sqlite3_errcode(db);
|
|
266
|
-
NSString *message = [NSString stringWithUTF8String:sqlite3_errmsg(db)];
|
|
267
|
-
return [NSString stringWithFormat:@"Error code %i: %@", code, message];
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
@end
|
package/unimodule.json
DELETED