expo-sqlite 11.7.0 → 11.8.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 +21 -0
- package/android/CMakeLists.txt +35 -0
- package/android/build.gradle +67 -29
- package/android/src/main/cpp/SQLite3Main.cpp +10 -0
- package/android/src/main/cpp/SQLite3Wrapper.cpp +253 -0
- package/android/src/main/cpp/SQLite3Wrapper.h +59 -0
- package/android/src/main/java/expo/modules/sqlite/SQLRecords.kt +8 -0
- package/android/src/main/java/expo/modules/sqlite/SQLite3Wrapper.kt +81 -0
- package/android/src/main/java/expo/modules/sqlite/SQLiteModule.kt +76 -185
- package/android/src/main/jniLibs/arm64/libcrsqlite.so +0 -0
- package/android/src/main/jniLibs/arm64-v8a/libcrsqlite.so +0 -0
- package/ios/ExpoSQLite.podspec +1 -1
- package/ios/crsqlite.xcframework/Info.plist +5 -5
- package/ios/crsqlite.xcframework/ios-arm64/crsqlite.framework/crsqlite +0 -0
- package/ios/crsqlite.xcframework/ios-arm64_x86_64-simulator/crsqlite.framework/crsqlite +0 -0
- package/package.json +2 -2
- package/vendor/README.expo.md +6 -0
- package/vendor/shell.c +28032 -0
- package/vendor/sqlite3.c +247629 -0
- package/vendor/sqlite3.h +13068 -0
- package/vendor/sqlite3ext.h +709 -0
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,26 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 11.8.0 — 2023-10-17
|
|
14
|
+
|
|
15
|
+
### 🛠 Breaking changes
|
|
16
|
+
|
|
17
|
+
- Dropped support for Android SDK 21 and 22. ([#24201](https://github.com/expo/expo/pull/24201) by [@behenate](https://github.com/behenate))
|
|
18
|
+
|
|
19
|
+
### 🎉 New features
|
|
20
|
+
|
|
21
|
+
- [Android] Rewrite implementations from low-level SQLite bindings. ([#24730](https://github.com/expo/expo/pull/24730) by [@kudo](https://github.com/kudo))
|
|
22
|
+
|
|
23
|
+
## 11.7.1 — 2023-09-18
|
|
24
|
+
|
|
25
|
+
### 🐛 Bug fixes
|
|
26
|
+
|
|
27
|
+
- Fix broken JS test. ([#24498](https://github.com/expo/expo/pull/24498) by [@alanjhughes](https://github.com/alanjhughes))
|
|
28
|
+
|
|
29
|
+
### 💡 Others
|
|
30
|
+
|
|
31
|
+
- [iOS] Bump `SQLite`version to latest. ([#24375](https://github.com/expo/expo/pull/24375) by [@alanjhughes](https://github.com/alanjhughes))
|
|
32
|
+
|
|
13
33
|
## 11.7.0 — 2023-09-15
|
|
14
34
|
|
|
15
35
|
### 🐛 Bug fixes
|
|
@@ -21,6 +41,7 @@
|
|
|
21
41
|
### 🎉 New features
|
|
22
42
|
|
|
23
43
|
- Add support for running raw queries on Android. ([#24320](https://github.com/expo/expo/pull/24320) by [@alanjhughes](https://github.com/alanjhughes))
|
|
44
|
+
- On Android, add support for `CRSQLite`. ([#24322](https://github.com/expo/expo/pull/24322) by [@alanjhughes](https://github.com/alanjhughes))
|
|
24
45
|
|
|
25
46
|
### 🐛 Bug fixes
|
|
26
47
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
cmake_minimum_required(VERSION 3.4.1)
|
|
2
|
+
|
|
3
|
+
project(expo-sqlite)
|
|
4
|
+
|
|
5
|
+
set(CMAKE_VERBOSE_MAKEFILE ON)
|
|
6
|
+
set(CMAKE_CXX_STANDARD 17)
|
|
7
|
+
set(PACKAGE_NAME "expo-sqlite")
|
|
8
|
+
set(BUILD_DIR ${CMAKE_SOURCE_DIR}/build)
|
|
9
|
+
|
|
10
|
+
set(SRC_DIR "${CMAKE_SOURCE_DIR}/src/main/cpp")
|
|
11
|
+
file(GLOB SOURCES "${SRC_DIR}/*.cpp")
|
|
12
|
+
|
|
13
|
+
add_library(
|
|
14
|
+
${PACKAGE_NAME}
|
|
15
|
+
SHARED
|
|
16
|
+
${SOURCES}
|
|
17
|
+
"${CMAKE_SOURCE_DIR}/../vendor/sqlite3.c"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
target_include_directories(
|
|
21
|
+
${PACKAGE_NAME}
|
|
22
|
+
PRIVATE
|
|
23
|
+
${SRC_DIR}
|
|
24
|
+
"${CMAKE_SOURCE_DIR}/../vendor"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
find_library(LOG_LIB log)
|
|
28
|
+
find_package(fbjni REQUIRED CONFIG)
|
|
29
|
+
|
|
30
|
+
target_link_libraries(
|
|
31
|
+
${PACKAGE_NAME}
|
|
32
|
+
${LOG_LIB}
|
|
33
|
+
fbjni::fbjni
|
|
34
|
+
android
|
|
35
|
+
)
|
package/android/build.gradle
CHANGED
|
@@ -3,15 +3,25 @@ apply plugin: 'kotlin-android'
|
|
|
3
3
|
apply plugin: 'maven-publish'
|
|
4
4
|
|
|
5
5
|
group = 'host.exp.exponent'
|
|
6
|
-
version = '11.
|
|
6
|
+
version = '11.8.0'
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
9
|
+
if (expoModulesCorePlugin.exists()) {
|
|
10
|
+
apply from: expoModulesCorePlugin
|
|
11
|
+
applyKotlinExpoModulesCorePlugin()
|
|
12
|
+
// Remove this check, but keep the contents after SDK49 support is dropped
|
|
13
|
+
if (safeExtGet("expoProvidesDefaultConfig", false)) {
|
|
14
|
+
useExpoPublishing()
|
|
15
|
+
useCoreDependencies()
|
|
13
16
|
}
|
|
17
|
+
}
|
|
14
18
|
|
|
19
|
+
def reactNativeArchitectures() {
|
|
20
|
+
def value = project.getProperties().get("reactNativeArchitectures")
|
|
21
|
+
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
buildscript {
|
|
15
25
|
// Simple helper that allows the root project to override versions declared by this library.
|
|
16
26
|
ext.safeExtGet = { prop, fallback ->
|
|
17
27
|
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
|
|
@@ -35,23 +45,44 @@ buildscript {
|
|
|
35
45
|
}
|
|
36
46
|
}
|
|
37
47
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
// Remove this if and it's contents, when support for SDK49 is dropped
|
|
49
|
+
if (!safeExtGet("expoProvidesDefaultConfig", false)) {
|
|
50
|
+
afterEvaluate {
|
|
51
|
+
publishing {
|
|
52
|
+
publications {
|
|
53
|
+
release(MavenPublication) {
|
|
54
|
+
from components.release
|
|
55
|
+
}
|
|
43
56
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
repositories {
|
|
58
|
+
maven {
|
|
59
|
+
url = mavenLocal().url
|
|
60
|
+
}
|
|
48
61
|
}
|
|
49
62
|
}
|
|
50
63
|
}
|
|
51
64
|
}
|
|
52
65
|
|
|
53
66
|
android {
|
|
54
|
-
|
|
67
|
+
// Remove this if and it's contents, when support for SDK49 is dropped
|
|
68
|
+
if (!safeExtGet("expoProvidesDefaultConfig", false)) {
|
|
69
|
+
compileSdkVersion safeExtGet("compileSdkVersion", 33)
|
|
70
|
+
|
|
71
|
+
defaultConfig {
|
|
72
|
+
minSdkVersion safeExtGet("minSdkVersion", 23)
|
|
73
|
+
targetSdkVersion safeExtGet("targetSdkVersion", 33)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
publishing {
|
|
77
|
+
singleVariant("release") {
|
|
78
|
+
withSourcesJar()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
lintOptions {
|
|
83
|
+
abortOnError false
|
|
84
|
+
}
|
|
85
|
+
}
|
|
55
86
|
|
|
56
87
|
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
|
|
57
88
|
if (agpVersion.tokenize('.')[0].toInteger() < 8) {
|
|
@@ -67,25 +98,32 @@ android {
|
|
|
67
98
|
|
|
68
99
|
namespace "expo.modules.sqlite"
|
|
69
100
|
defaultConfig {
|
|
70
|
-
minSdkVersion safeExtGet("minSdkVersion", 21)
|
|
71
|
-
targetSdkVersion safeExtGet("targetSdkVersion", 33)
|
|
72
101
|
versionCode 18
|
|
73
|
-
versionName "11.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
102
|
+
versionName "11.8.0"
|
|
103
|
+
|
|
104
|
+
externalNativeBuild {
|
|
105
|
+
cmake {
|
|
106
|
+
abiFilters (*reactNativeArchitectures())
|
|
107
|
+
arguments "-DANDROID_STL=c++_shared"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
77
110
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
111
|
+
externalNativeBuild {
|
|
112
|
+
cmake {
|
|
113
|
+
path "CMakeLists.txt"
|
|
81
114
|
}
|
|
82
115
|
}
|
|
116
|
+
buildFeatures {
|
|
117
|
+
prefab true
|
|
118
|
+
}
|
|
83
119
|
}
|
|
84
120
|
|
|
85
121
|
dependencies {
|
|
86
|
-
|
|
122
|
+
// Remove this if and it's contents, when support for SDK49 is dropped
|
|
123
|
+
if (!safeExtGet("expoProvidesDefaultConfig", false)) {
|
|
124
|
+
implementation project(':expo-modules-core')
|
|
125
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
|
126
|
+
}
|
|
87
127
|
|
|
88
|
-
|
|
89
|
-
implementation "androidx.sqlite:sqlite-ktx:2.3.1"
|
|
90
|
-
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
|
|
128
|
+
compileOnly 'com.facebook.fbjni:fbjni:0.3.0'
|
|
91
129
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
#include <fbjni/fbjni.h>
|
|
4
|
+
|
|
5
|
+
#include "SQLite3Wrapper.h"
|
|
6
|
+
|
|
7
|
+
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *) {
|
|
8
|
+
return facebook::jni::initialize(
|
|
9
|
+
vm, [] { expo::SQLite3Wrapper::registerNatives(); });
|
|
10
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
#include "SQLite3Wrapper.h"
|
|
4
|
+
|
|
5
|
+
#include <android/log.h>
|
|
6
|
+
|
|
7
|
+
namespace jni = facebook::jni;
|
|
8
|
+
|
|
9
|
+
namespace expo {
|
|
10
|
+
|
|
11
|
+
namespace {
|
|
12
|
+
|
|
13
|
+
constexpr char TAG[] = "expo-sqlite";
|
|
14
|
+
|
|
15
|
+
} // namespace
|
|
16
|
+
|
|
17
|
+
// static
|
|
18
|
+
void SQLite3Wrapper::registerNatives() {
|
|
19
|
+
registerHybrid({
|
|
20
|
+
makeNativeMethod("initHybrid", SQLite3Wrapper::initHybrid),
|
|
21
|
+
makeNativeMethod("executeSql", SQLite3Wrapper::executeSql),
|
|
22
|
+
makeNativeMethod("sqlite3_open", SQLite3Wrapper::sqlite3_open),
|
|
23
|
+
makeNativeMethod("sqlite3_close", SQLite3Wrapper::sqlite3_close),
|
|
24
|
+
makeNativeMethod("sqlite3_enable_load_extension",
|
|
25
|
+
SQLite3Wrapper::sqlite3_enable_load_extension),
|
|
26
|
+
makeNativeMethod("sqlite3_load_extension",
|
|
27
|
+
SQLite3Wrapper::sqlite3_load_extension),
|
|
28
|
+
makeNativeMethod("sqlite3_update_hook",
|
|
29
|
+
SQLite3Wrapper::sqlite3_update_hook),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
jni::local_ref<jni::JList<jni::JObject>>
|
|
34
|
+
SQLite3Wrapper::executeSql(const std::string &sql,
|
|
35
|
+
jni::alias_ref<jni::JList<jni::JObject>> args,
|
|
36
|
+
bool readOnly) {
|
|
37
|
+
auto resultRows = jni::JArrayList<jni::JObject>::create();
|
|
38
|
+
sqlite3_stmt *statement = nullptr;
|
|
39
|
+
int rowsAffected = 0;
|
|
40
|
+
sqlite3_int64 insertId = 0;
|
|
41
|
+
jni::local_ref<jni::JString> error;
|
|
42
|
+
|
|
43
|
+
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &statement, nullptr) !=
|
|
44
|
+
SQLITE_OK) {
|
|
45
|
+
auto results = jni::JArrayList<jni::JObject>::create();
|
|
46
|
+
results->add(convertSqlLiteErrorToString(db));
|
|
47
|
+
return results;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
bool queryIsReadOnly = sqlite3_stmt_readonly(statement) > 0;
|
|
51
|
+
if (readOnly && !queryIsReadOnly) {
|
|
52
|
+
auto results = jni::JArrayList<jni::JObject>::create();
|
|
53
|
+
std::string error("could not prepare ");
|
|
54
|
+
error += sql;
|
|
55
|
+
results->add(jni::make_jstring(error));
|
|
56
|
+
return results;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
int index = 1;
|
|
60
|
+
for (const auto &arg : *args) {
|
|
61
|
+
bindStatement(statement, arg, index++);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
int columnCount = 0;
|
|
65
|
+
auto columnNames = jni::JArrayList<jni::JString>::create();
|
|
66
|
+
int columnType;
|
|
67
|
+
bool fetchedColumns = false;
|
|
68
|
+
jni::local_ref<jni::JObject> value;
|
|
69
|
+
bool hasMore = true;
|
|
70
|
+
|
|
71
|
+
while (hasMore) {
|
|
72
|
+
switch (sqlite3_step(statement)) {
|
|
73
|
+
case SQLITE_ROW: {
|
|
74
|
+
if (!fetchedColumns) {
|
|
75
|
+
columnCount = sqlite3_column_count(statement);
|
|
76
|
+
|
|
77
|
+
for (int i = 0; i < columnCount; ++i) {
|
|
78
|
+
const char *columnName = sqlite3_column_name(statement, i);
|
|
79
|
+
columnNames->add(jni::make_jstring(columnName));
|
|
80
|
+
}
|
|
81
|
+
fetchedColumns = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
auto entry = jni::JArrayList<jni::JObject>::create();
|
|
85
|
+
|
|
86
|
+
for (int i = 0; i < columnCount; ++i) {
|
|
87
|
+
columnType = sqlite3_column_type(statement, i);
|
|
88
|
+
value = getSqlValue(columnType, statement, i);
|
|
89
|
+
entry->add(value);
|
|
90
|
+
}
|
|
91
|
+
resultRows->add(entry);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
case SQLITE_DONE: {
|
|
95
|
+
hasMore = false;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
default: {
|
|
99
|
+
error = convertSqlLiteErrorToString(db);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!queryIsReadOnly) {
|
|
106
|
+
rowsAffected = sqlite3_changes(db);
|
|
107
|
+
if (rowsAffected > 0) {
|
|
108
|
+
insertId = sqlite3_last_insert_rowid(db);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
sqlite3_finalize(statement);
|
|
113
|
+
|
|
114
|
+
if (error) {
|
|
115
|
+
auto results = jni::JArrayList<jni::JObject>::create();
|
|
116
|
+
results->add(error);
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
auto results = jni::JArrayList<jni::JObject>::create();
|
|
121
|
+
results->add(nullptr);
|
|
122
|
+
results->add(jni::JLong::valueOf(insertId));
|
|
123
|
+
results->add(jni::JInteger::valueOf(rowsAffected));
|
|
124
|
+
results->add(columnNames);
|
|
125
|
+
results->add(resultRows);
|
|
126
|
+
return results;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
int SQLite3Wrapper::sqlite3_open(const std::string &dbPath) {
|
|
130
|
+
return ::sqlite3_open(dbPath.c_str(), &db);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
int SQLite3Wrapper::sqlite3_close() {
|
|
134
|
+
int ret = ::sqlite3_close(db);
|
|
135
|
+
db = nullptr;
|
|
136
|
+
return ret;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
int SQLite3Wrapper::sqlite3_enable_load_extension(int onoff) {
|
|
140
|
+
return ::sqlite3_enable_load_extension(db, onoff);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
int SQLite3Wrapper::sqlite3_load_extension(const std::string &libPath,
|
|
144
|
+
const std::string &entryProc) {
|
|
145
|
+
char *errorMessage;
|
|
146
|
+
int ret = ::sqlite3_load_extension(db, libPath.c_str(), entryProc.c_str(),
|
|
147
|
+
&errorMessage);
|
|
148
|
+
if (errorMessage) {
|
|
149
|
+
__android_log_write(ANDROID_LOG_ERROR, TAG, errorMessage);
|
|
150
|
+
::sqlite3_free(errorMessage);
|
|
151
|
+
}
|
|
152
|
+
return ret;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
void SQLite3Wrapper::sqlite3_update_hook(bool enabled) {
|
|
156
|
+
if (enabled) {
|
|
157
|
+
::sqlite3_update_hook(db, SQLite3Wrapper::OnUpdateHook, this);
|
|
158
|
+
} else {
|
|
159
|
+
::sqlite3_update_hook(db, nullptr, nullptr);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// static
|
|
164
|
+
jni::local_ref<SQLite3Wrapper::jhybriddata>
|
|
165
|
+
SQLite3Wrapper::initHybrid(jni::alias_ref<jhybridobject> jThis) {
|
|
166
|
+
return makeCxxInstance(jThis);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// static
|
|
170
|
+
jni::local_ref<jni::JString>
|
|
171
|
+
SQLite3Wrapper::convertSqlLiteErrorToString(sqlite3 *db) {
|
|
172
|
+
int code = sqlite3_errcode(db);
|
|
173
|
+
const char *message = sqlite3_errmsg(db);
|
|
174
|
+
std::string result("Error code ");
|
|
175
|
+
result += code;
|
|
176
|
+
result += ": ";
|
|
177
|
+
result += message;
|
|
178
|
+
return jni::make_jstring(result);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// static
|
|
182
|
+
void SQLite3Wrapper::bindStatement(sqlite3_stmt *statement,
|
|
183
|
+
jni::alias_ref<jni::JObject> arg,
|
|
184
|
+
int index) {
|
|
185
|
+
static const auto integerClass = jni::JInteger::javaClassStatic();
|
|
186
|
+
static const auto longClass = jni::JLong::javaClassStatic();
|
|
187
|
+
static const auto doubleClass = jni::JDouble::javaClassStatic();
|
|
188
|
+
static const auto stringClass = jni::JString::javaClassStatic();
|
|
189
|
+
|
|
190
|
+
if (arg == nullptr) {
|
|
191
|
+
sqlite3_bind_null(statement, index);
|
|
192
|
+
} else if (arg->isInstanceOf(integerClass)) {
|
|
193
|
+
sqlite3_bind_int(statement, index,
|
|
194
|
+
jni::static_ref_cast<jni::JInteger>(arg)->value());
|
|
195
|
+
} else if (arg->isInstanceOf(longClass)) {
|
|
196
|
+
sqlite3_bind_int64(statement, index,
|
|
197
|
+
jni::static_ref_cast<jni::JLong>(arg)->value());
|
|
198
|
+
} else if (arg->isInstanceOf(doubleClass)) {
|
|
199
|
+
sqlite3_bind_double(statement, index,
|
|
200
|
+
jni::static_ref_cast<jni::JDouble>(arg)->value());
|
|
201
|
+
} else {
|
|
202
|
+
std::string stringArg;
|
|
203
|
+
if (arg->isInstanceOf(stringClass)) {
|
|
204
|
+
stringArg = jni::static_ref_cast<jni::JString>(arg)->toStdString();
|
|
205
|
+
} else {
|
|
206
|
+
stringArg = arg->toString();
|
|
207
|
+
}
|
|
208
|
+
sqlite3_bind_text(statement, index, stringArg.c_str(), stringArg.length(),
|
|
209
|
+
SQLITE_TRANSIENT);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// static
|
|
214
|
+
jni::local_ref<jni::JObject>
|
|
215
|
+
SQLite3Wrapper::getSqlValue(int columnType, sqlite3_stmt *statement,
|
|
216
|
+
int index) {
|
|
217
|
+
switch (columnType) {
|
|
218
|
+
case SQLITE_INTEGER: {
|
|
219
|
+
return jni::JLong::valueOf(sqlite3_column_int64(statement, index));
|
|
220
|
+
}
|
|
221
|
+
case SQLITE_FLOAT: {
|
|
222
|
+
return jni::JDouble::valueOf(sqlite3_column_double(statement, index));
|
|
223
|
+
}
|
|
224
|
+
case SQLITE_BLOB: {
|
|
225
|
+
JNIEnv *env = jni::Environment::current();
|
|
226
|
+
return jni::adopt_local(env->NewString(
|
|
227
|
+
reinterpret_cast<const jchar *>(sqlite3_column_blob(statement, index)),
|
|
228
|
+
static_cast<size_t>(sqlite3_column_bytes(statement, index))));
|
|
229
|
+
}
|
|
230
|
+
case SQLITE_TEXT: {
|
|
231
|
+
std::string text(
|
|
232
|
+
reinterpret_cast<const char *>(sqlite3_column_text(statement, index)),
|
|
233
|
+
static_cast<size_t>(sqlite3_column_bytes(statement, index)));
|
|
234
|
+
return jni::make_jstring(text);
|
|
235
|
+
}
|
|
236
|
+
default: {
|
|
237
|
+
return nullptr;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// static
|
|
243
|
+
void SQLite3Wrapper::OnUpdateHook(void *arg, int action, char const *dbName,
|
|
244
|
+
char const *tableName, sqlite3_int64 rowId) {
|
|
245
|
+
SQLite3Wrapper *pThis = reinterpret_cast<SQLite3Wrapper *>(arg);
|
|
246
|
+
static const auto method =
|
|
247
|
+
jni::findClassStatic("expo/modules/sqlite/SQLite3Wrapper")
|
|
248
|
+
->getMethod<void(jint, jstring, jstring, jlong)>("onUpdate");
|
|
249
|
+
method(pThis->javaPart_, action, jni::make_jstring(dbName).get(),
|
|
250
|
+
jni::make_jstring(tableName).get(), rowId);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
} // namespace expo
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Copyright 2015-present 650 Industries. All rights reserved.
|
|
2
|
+
|
|
3
|
+
#pragma once
|
|
4
|
+
|
|
5
|
+
#include <fbjni/fbjni.h>
|
|
6
|
+
#include <string>
|
|
7
|
+
|
|
8
|
+
#include "sqlite3.h"
|
|
9
|
+
|
|
10
|
+
namespace jni = facebook::jni;
|
|
11
|
+
|
|
12
|
+
namespace expo {
|
|
13
|
+
|
|
14
|
+
class SQLite3Wrapper : public jni::HybridClass<SQLite3Wrapper> {
|
|
15
|
+
public:
|
|
16
|
+
static constexpr auto kJavaDescriptor =
|
|
17
|
+
"Lexpo/modules/sqlite/SQLite3Wrapper;";
|
|
18
|
+
|
|
19
|
+
static void registerNatives();
|
|
20
|
+
|
|
21
|
+
jni::local_ref<jni::JList<jni::JObject>>
|
|
22
|
+
executeSql(const std::string &sql,
|
|
23
|
+
jni::alias_ref<jni::JList<jni::JObject>> args, bool readOnly);
|
|
24
|
+
|
|
25
|
+
// sqlite3 bindings
|
|
26
|
+
int sqlite3_open(const std::string &dbPath);
|
|
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
|
+
|
|
33
|
+
private:
|
|
34
|
+
explicit SQLite3Wrapper(jni::alias_ref<SQLite3Wrapper::jhybridobject> jThis)
|
|
35
|
+
: javaPart_(jni::make_global(jThis)) {}
|
|
36
|
+
|
|
37
|
+
private:
|
|
38
|
+
static jni::local_ref<jhybriddata>
|
|
39
|
+
initHybrid(jni::alias_ref<jhybridobject> jThis);
|
|
40
|
+
|
|
41
|
+
static jni::local_ref<jni::JString> convertSqlLiteErrorToString(sqlite3 *db);
|
|
42
|
+
|
|
43
|
+
static void bindStatement(sqlite3_stmt *statement,
|
|
44
|
+
jni::alias_ref<jni::JObject> arg, int index);
|
|
45
|
+
|
|
46
|
+
static jni::local_ref<jni::JObject>
|
|
47
|
+
getSqlValue(int columnType, sqlite3_stmt *statement, int index);
|
|
48
|
+
|
|
49
|
+
static void OnUpdateHook(void *arg, int action, char const *dbName,
|
|
50
|
+
char const *tableName, sqlite3_int64 rowId);
|
|
51
|
+
|
|
52
|
+
private:
|
|
53
|
+
friend HybridBase;
|
|
54
|
+
|
|
55
|
+
jni::global_ref<SQLite3Wrapper::javaobject> javaPart_;
|
|
56
|
+
sqlite3 *db;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
} // namespace expo
|
|
@@ -2,6 +2,7 @@ package expo.modules.sqlite
|
|
|
2
2
|
|
|
3
3
|
import expo.modules.kotlin.records.Field
|
|
4
4
|
import expo.modules.kotlin.records.Record
|
|
5
|
+
import expo.modules.kotlin.types.Enumerable
|
|
5
6
|
|
|
6
7
|
data class Query(
|
|
7
8
|
@Field
|
|
@@ -9,3 +10,10 @@ data class Query(
|
|
|
9
10
|
@Field
|
|
10
11
|
val args: List<Any?>
|
|
11
12
|
) : Record
|
|
13
|
+
|
|
14
|
+
enum class SqlAction(val value: String) : Enumerable {
|
|
15
|
+
INSERT("insert"),
|
|
16
|
+
UPDATE("update"),
|
|
17
|
+
DELETE("delete"),
|
|
18
|
+
UNKNOWN("unknown")
|
|
19
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
package expo.modules.sqlite
|
|
2
|
+
|
|
3
|
+
import com.facebook.jni.HybridData
|
|
4
|
+
import expo.modules.core.interfaces.DoNotStrip
|
|
5
|
+
|
|
6
|
+
private typealias UpdateListener = (tableName: String, operationType: Int, rowID: Long) -> Unit
|
|
7
|
+
|
|
8
|
+
@Suppress("KotlinJniMissingFunction")
|
|
9
|
+
@DoNotStrip
|
|
10
|
+
class SQLite3Wrapper private constructor() {
|
|
11
|
+
@DoNotStrip
|
|
12
|
+
private val mHybridData: HybridData
|
|
13
|
+
|
|
14
|
+
private var mUpdateListener: UpdateListener? = null
|
|
15
|
+
|
|
16
|
+
init {
|
|
17
|
+
mHybridData = initHybrid()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Execute SQL commands
|
|
22
|
+
*/
|
|
23
|
+
external fun executeSql(sql: String, args: List<Any?>, readOnly: Boolean): List<Any>
|
|
24
|
+
|
|
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
|
+
// region sqlite3 bindings
|
|
42
|
+
|
|
43
|
+
external fun sqlite3_open(dbPath: String): Int
|
|
44
|
+
external fun sqlite3_close(): Int
|
|
45
|
+
|
|
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
|
+
// endregion
|
|
52
|
+
|
|
53
|
+
// region internals
|
|
54
|
+
|
|
55
|
+
private external fun initHybrid(): HybridData
|
|
56
|
+
|
|
57
|
+
@DoNotStrip
|
|
58
|
+
private fun onUpdate(action: Int, dbName: String, tableName: String, rowId: Long) {
|
|
59
|
+
mUpdateListener?.invoke(tableName, action, rowId)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// endregion
|
|
63
|
+
|
|
64
|
+
companion object {
|
|
65
|
+
init {
|
|
66
|
+
System.loadLibrary("expo-sqlite")
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@JvmStatic
|
|
70
|
+
fun open(dbPath: String): SQLite3Wrapper? {
|
|
71
|
+
val instance = SQLite3Wrapper()
|
|
72
|
+
if (instance.sqlite3_open(dbPath) != SQLITE_OK) {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
return instance
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// These error code should be synced with sqlite3.h
|
|
79
|
+
const val SQLITE_OK = 0
|
|
80
|
+
}
|
|
81
|
+
}
|