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 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
+ )
@@ -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.7.0'
6
+ version = '11.8.0'
7
7
 
8
- buildscript {
9
- def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
10
- if (expoModulesCorePlugin.exists()) {
11
- apply from: expoModulesCorePlugin
12
- applyKotlinExpoModulesCorePlugin()
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
- afterEvaluate {
39
- publishing {
40
- publications {
41
- release(MavenPublication) {
42
- from components.release
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
- repositories {
46
- maven {
47
- url = mavenLocal().url
57
+ repositories {
58
+ maven {
59
+ url = mavenLocal().url
60
+ }
48
61
  }
49
62
  }
50
63
  }
51
64
  }
52
65
 
53
66
  android {
54
- compileSdkVersion safeExtGet("compileSdkVersion", 33)
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.7.0"
74
- }
75
- lintOptions {
76
- abortOnError false
102
+ versionName "11.8.0"
103
+
104
+ externalNativeBuild {
105
+ cmake {
106
+ abiFilters (*reactNativeArchitectures())
107
+ arguments "-DANDROID_STL=c++_shared"
108
+ }
109
+ }
77
110
  }
78
- publishing {
79
- singleVariant("release") {
80
- withSourcesJar()
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
- implementation project(':expo-modules-core')
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
- implementation 'com.github.requery:sqlite-android:3.39.2'
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
+ }