@op-engineering/op-sqlite 2.0.1 → 2.0.3

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/README.md CHANGED
@@ -22,6 +22,8 @@ You can find the [benchmarking code in the example app](https://github.com/OP-En
22
22
 
23
23
  Memory consumption is also 1/4 compared to `react-native-quick-sqlite`. This query used to take 1.2 GB of peak memory usage, and now runs in 250mbs.
24
24
 
25
+ You can also turn on [Memory Mapping](#speed) to make your queries even faster by skipping the kernel during I/O and potentially reduce RAM usage, this comes with some disadvantages though. If you want even more speed and you can re-use your queries you can use [Prepared Statements](#prepared-statements).
26
+
25
27
  # Encryption
26
28
 
27
29
  If you need to encrypt your entire database, there is [`op-sqlcipher`](https://github.com/OP-Engineering/op-sqlcipher), which is a fork of this library that uses [SQLCipher](https://github.com/sqlcipher/sqlcipher). It completely encrypts the database with minimal overhead.
@@ -102,6 +104,25 @@ const largeDb = open({
102
104
  });
103
105
  ```
104
106
 
107
+ # Speed
108
+
109
+ op-sqlite is already the fastest solution it can be, but it doesn't mean you cannot tweak SQLite to be faster (at the cost of some disadvantages). One possible tweak is turning on [Memory Mapping](https://www.sqlite.org/mmap.html). It allows to read/write to/from the disk without going through the kernel. However, if your queries throw an error your application might crash.
110
+
111
+ To turn on Memory Mapping, execute the following pragma statement after opening a db:
112
+
113
+ ```ts
114
+ const db = open({
115
+ name: 'mydb.sqlite',
116
+ });
117
+
118
+ // 0 turns of memory mapping, any other number enables it with the cache size
119
+ db.execute('PRAGMA mmap_size=268435456');
120
+ ```
121
+
122
+ If you use prepared statements plus memory mapping, you can get to inches of MMKV for the most performance critical queries, here is a simple example writing/reading a single value.
123
+
124
+ ![mmkv comparison](mmkv.png)
125
+
105
126
  # API
106
127
 
107
128
  ```typescript
@@ -134,7 +155,7 @@ db = {
134
155
  }
135
156
  ```
136
157
 
137
- ### Simple queries
158
+ ## Simple queries
138
159
 
139
160
  The basic query is **synchronous**, it will block rendering on large operations, further below you will find async versions.
140
161
 
@@ -162,7 +183,7 @@ try {
162
183
  }
163
184
  ```
164
185
 
165
- ### Multiple statements in a single string
186
+ ## Multiple statements in a single string
166
187
 
167
188
  You can execute multiple statements in a single operation. The API however is not really thought for this use case and the results (and their metadata) will be mangled, so you can discard it.
168
189
 
@@ -186,7 +207,7 @@ let t2name = db.execute(
186
207
  console.log(t2name.rows?._array[0].name); // outputs "T2"
187
208
  ```
188
209
 
189
- ### Transactions
210
+ ## Transactions
190
211
 
191
212
  Throwing an error inside the callback will ROLLBACK the transaction.
192
213
 
@@ -215,7 +236,7 @@ await db.transaction('myDatabase', (tx) => {
215
236
  });
216
237
  ```
217
238
 
218
- ### Batch operation
239
+ ## Batch operation
219
240
 
220
241
  Batch execution allows the transactional execution of a set of commands
221
242
 
@@ -232,7 +253,7 @@ const res = db.executeSqlBatch('myDatabase', commands);
232
253
  console.log(`Batch affected ${result.rowsAffected} rows`);
233
254
  ```
234
255
 
235
- ### Dynamic Column Metadata
256
+ ## Dynamic Column Metadata
236
257
 
237
258
  In some scenarios, dynamic applications may need to get some metadata information about the returned result set.
238
259
 
@@ -253,7 +274,7 @@ metadata.forEach((column) => {
253
274
  });
254
275
  ```
255
276
 
256
- ### Async operations
277
+ ## Async operations
257
278
 
258
279
  You might have too much SQL to process and it will cause your application to freeze. There are async versions for some of the operations. This will offload the SQLite processing to a different thread.
259
280
 
@@ -267,7 +288,7 @@ db.executeAsync(
267
288
  );
268
289
  ```
269
290
 
270
- ### Blobs
291
+ ## Blobs
271
292
 
272
293
  Blobs are supported via `ArrayBuffer`, you need to be careful about the semantics though. You cannot instantiate an instance of `ArrayBuffer` directly, nor pass a typed array directly. Here is an example:
273
294
 
@@ -295,7 +316,22 @@ const result = db.execute('SELECT content FROM BlobTable');
295
316
  const finalUint8 = new Uint8Array(result.rows!._array[0].content);
296
317
  ```
297
318
 
298
- ### Attach or Detach other databases
319
+ ## Prepared statements
320
+
321
+ A lot of the work when executing queries is not iterating through the result set itself but, sometimes, planning the execution. If you have a query which is expensive but you can re-use (even if you have to change the arguments) you can use a `prepared statement`:
322
+
323
+ ```ts
324
+ const statement = db.prepareStatement('SELECT * FROM User WHERE name = ?;');
325
+ statement.bind(['Oscar']);
326
+ let results1 = statement.execute();
327
+
328
+ statement.bind(['Carlos']);
329
+ let results2 = statement.execute();
330
+ ```
331
+
332
+ You only pay the price of parsing the query once, and each subsequent execution should be faster.
333
+
334
+ # Attach or Detach other databases
299
335
 
300
336
  SQLite supports attaching or detaching other database files into your main database connection through an alias.
301
337
  You can do any operation you like on this attached database like JOIN results across tables in different schemas, or update data or objects.
@@ -322,7 +358,7 @@ if (!detachResult.status) {
322
358
  }
323
359
  ```
324
360
 
325
- ### Loading SQL Dump Files
361
+ # Loading SQL Dump Files
326
362
 
327
363
  If you have a SQL dump file, you can load it directly, with low memory consumption:
328
364
 
@@ -334,7 +370,7 @@ const { rowsAffected, commands } = db
334
370
  });
335
371
  ```
336
372
 
337
- ## Hooks
373
+ # Hooks
338
374
 
339
375
  You can subscribe to changes in your database by using an update hook:
340
376
 
@@ -396,7 +432,7 @@ db.commitHook(null);
396
432
  db.rollbackHook(null);
397
433
  ```
398
434
 
399
- ## Use built-in SQLite
435
+ # Use built-in SQLite
400
436
 
401
437
  On iOS you can use the embedded SQLite, when running `pod-install` add an environment flag:
402
438
 
@@ -406,11 +442,11 @@ OP_SQLITE_USE_PHONE_VERSION=1 npx pod-install
406
442
 
407
443
  On Android, it is not possible to link the OS SQLite. It is also a bad idea due to vendor changes, old android bugs, etc. Unfortunately, this means this library will add some megabytes to your app size.
408
444
 
409
- ## Enable compile-time options
445
+ # Enable compile-time options
410
446
 
411
447
  By specifying pre-processor flags, you can enable optional features like FTS5, Geopoly, etc.
412
448
 
413
- ### iOS
449
+ ## iOS
414
450
 
415
451
  Add a `post_install` block to your `<PROJECT_ROOT>/ios/Podfile` like so:
416
452
 
@@ -429,7 +465,7 @@ end
429
465
  Replace the `<SQLITE_FLAGS>` part with the flags you want to add.
430
466
  For example, you could add `SQLITE_ENABLE_FTS5=1` to `GCC_PREPROCESSOR_DEFINITIONS` to enable FTS5 in the iOS project.
431
467
 
432
- ### Android
468
+ ## Android
433
469
 
434
470
  You can specify flags via `<PROJECT_ROOT>/android/gradle.properties` like so:
435
471
 
@@ -437,14 +473,18 @@ You can specify flags via `<PROJECT_ROOT>/android/gradle.properties` like so:
437
473
  OPSQLiteFlags="-DSQLITE_ENABLE_FTS5=1"
438
474
  ```
439
475
 
440
- ## Additional configuration
476
+ # Additional configuration
441
477
 
442
- ### App groups (iOS only)
478
+ ## App groups (iOS only)
443
479
 
444
480
  On iOS, the SQLite database can be placed in an app group, in order to make it accessible from other apps in that app group. E.g. for sharing capabilities.
445
481
 
446
482
  To use an app group, add the app group ID as the value for the `OPSQLite_AppGroup` key in your project's `Info.plist` file. You'll also need to configure the app group in your project settings. (Xcode -> Project Settings -> Signing & Capabilities -> Add Capability -> App Groups)
447
483
 
448
- ## License
484
+ # Contribute
485
+
486
+ You need to have clang-format installed (`brew install clang-format`)
487
+
488
+ # License
449
489
 
450
490
  MIT License.
@@ -29,8 +29,10 @@ add_library(
29
29
  ../cpp/ThreadPool.cpp
30
30
  ../cpp/sqlbatchexecutor.h
31
31
  ../cpp/sqlbatchexecutor.cpp
32
- ../cpp/DynamicHostObject.cpp
33
- ../cpp/DynamicHostObject.h
32
+ ../cpp/SmartHostObject.cpp
33
+ ../cpp/SmartHostObject.h
34
+ ../cpp/PreparedStatementHostObject.h
35
+ ../cpp/PreparedStatementHostObject.cpp
34
36
  ../cpp/DumbHostObject.cpp
35
37
  ../cpp/DumbHostObject.h
36
38
  ../cpp/macros.h
@@ -1,37 +1,42 @@
1
1
  #include "DumbHostObject.h"
2
+ #include "SmartHostObject.h"
2
3
  #include "utils.h"
3
4
  #include <iostream>
4
5
 
5
6
  namespace opsqlite {
6
7
 
7
- namespace jsi = facebook::jsi;
8
-
9
- DumbHostObject::DumbHostObject(std::shared_ptr<std::vector<DynamicHostObject>> metadata) {
10
- this->metadata = metadata;
11
- };
12
-
13
- std::vector<jsi::PropNameID> DumbHostObject::getPropertyNames(jsi::Runtime &rt) {
14
- std::vector<jsi::PropNameID> keys;
15
-
16
- for (auto field : *metadata) {
17
- // TODO improve this by generating the propName once on metadata creation
18
- keys.push_back(jsi::PropNameID::forAscii(rt, std::get<std::string>(field.fields[0].second)));
19
- }
20
-
21
- return keys;
22
- }
8
+ namespace jsi = facebook::jsi;
9
+
10
+ DumbHostObject::DumbHostObject(
11
+ std::shared_ptr<std::vector<SmartHostObject>> metadata) {
12
+ this->metadata = metadata;
13
+ };
14
+
15
+ std::vector<jsi::PropNameID>
16
+ DumbHostObject::getPropertyNames(jsi::Runtime &rt) {
17
+ std::vector<jsi::PropNameID> keys;
18
+
19
+ for (auto field : *metadata) {
20
+ // TODO improve this by generating the propName once on metadata creation
21
+ keys.push_back(jsi::PropNameID::forAscii(
22
+ rt, std::get<std::string>(field.fields[0].second)));
23
+ }
23
24
 
24
- jsi::Value DumbHostObject::get(jsi::Runtime &rt, const jsi::PropNameID &propNameID) {
25
- auto name = propNameID.utf8(rt);
26
- auto fields = metadata.get();
27
- for(int i = 0; i < fields->size(); i++) {
28
- auto fieldName = std::get<std::string>(fields->at(i).fields[0].second);
29
- if(fieldName == name) {
30
- return toJSI(rt, values.at(i));
31
- }
32
- }
33
-
34
- return {};
25
+ return keys;
26
+ }
27
+
28
+ jsi::Value DumbHostObject::get(jsi::Runtime &rt,
29
+ const jsi::PropNameID &propNameID) {
30
+ auto name = propNameID.utf8(rt);
31
+ auto fields = metadata.get();
32
+ for (int i = 0; i < fields->size(); i++) {
33
+ auto fieldName = std::get<std::string>(fields->at(i).fields[0].second);
34
+ if (fieldName == name) {
35
+ return toJSI(rt, values.at(i));
35
36
  }
37
+ }
36
38
 
39
+ return {};
37
40
  }
41
+
42
+ } // namespace opsqlite
@@ -3,31 +3,31 @@
3
3
 
4
4
  #include <stdio.h>
5
5
 
6
- #include <jsi/jsi.h>
6
+ #include "SmartHostObject.h"
7
+ #include "types.h"
7
8
  #include <any>
9
+ #include <jsi/jsi.h>
8
10
  #include <vector>
9
- #include "types.h"
10
- #include "DynamicHostObject.h"
11
11
 
12
12
  namespace opsqlite {
13
13
 
14
- namespace jsi = facebook::jsi;
14
+ namespace jsi = facebook::jsi;
15
+
16
+ class JSI_EXPORT DumbHostObject : public jsi::HostObject {
17
+ public:
18
+ DumbHostObject(){};
19
+
20
+ DumbHostObject(std::shared_ptr<std::vector<SmartHostObject>> metadata);
15
21
 
16
- class JSI_EXPORT DumbHostObject: public jsi::HostObject {
17
- public:
18
- DumbHostObject() {};
19
-
20
- DumbHostObject(std::shared_ptr<std::vector<DynamicHostObject>> metadata);
22
+ std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt);
21
23
 
22
- std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt);
24
+ jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID);
23
25
 
24
- jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID);
26
+ std::vector<JSVariant> values;
25
27
 
26
- std::vector<JSVariant> values;
27
-
28
- std::shared_ptr<std::vector<DynamicHostObject>> metadata;
29
- };
28
+ std::shared_ptr<std::vector<SmartHostObject>> metadata;
29
+ };
30
30
 
31
- }
31
+ } // namespace opsqlite
32
32
 
33
33
  #endif /* DumbHostObject_h */
@@ -0,0 +1,88 @@
1
+ //
2
+ // PreparedStatementHostObject.cpp
3
+ // op-sqlite
4
+ //
5
+ // Created by Oscar Franco on 5/12/23.
6
+ //
7
+
8
+ #include "PreparedStatementHostObject.h"
9
+ #include "bridge.h"
10
+ #include "macros.h"
11
+ #include "utils.h"
12
+
13
+ namespace opsqlite {
14
+
15
+ namespace jsi = facebook::jsi;
16
+
17
+ PreparedStatementHostObject::PreparedStatementHostObject(
18
+ std::string dbName, sqlite3_stmt *statementPtr)
19
+ : _dbName(dbName), _statement(statementPtr) {}
20
+
21
+ std::vector<jsi::PropNameID>
22
+ PreparedStatementHostObject::getPropertyNames(jsi::Runtime &rt) {
23
+ std::vector<jsi::PropNameID> keys;
24
+
25
+ // for (auto field : fields) {
26
+ // keys.push_back(jsi::PropNameID::forAscii(rt, field.first));
27
+ // }
28
+
29
+ return keys;
30
+ }
31
+
32
+ jsi::Value PreparedStatementHostObject::get(jsi::Runtime &rt,
33
+ const jsi::PropNameID &propNameID) {
34
+ auto name = propNameID.utf8(rt);
35
+
36
+ if (name == "bind") {
37
+ return HOSTFN("bind", 1) {
38
+ if (_statement == NULL) {
39
+ throw std::runtime_error("statement has been freed");
40
+ }
41
+
42
+ std::vector<JSVariant> params;
43
+
44
+ const jsi::Value &originalParams = args[0];
45
+ params = toVariantVec(rt, originalParams);
46
+
47
+ std::vector<DumbHostObject> results;
48
+ std::shared_ptr<std::vector<SmartHostObject>> metadata =
49
+ std::make_shared<std::vector<SmartHostObject>>();
50
+
51
+ sqlite_bind_statement(_statement, &params);
52
+
53
+ return {};
54
+ });
55
+ }
56
+
57
+ if (name == "execute") {
58
+ return HOSTFN("execute", 1) {
59
+ if (_statement == NULL) {
60
+ throw std::runtime_error("statement has been freed");
61
+ }
62
+ std::vector<DumbHostObject> results;
63
+ std::shared_ptr<std::vector<SmartHostObject>> metadata =
64
+ std::make_shared<std::vector<SmartHostObject>>();
65
+
66
+ auto status = sqlite_execute_prepared_statement(_dbName, _statement,
67
+ &results, metadata);
68
+
69
+ if (status.type == SQLiteError) {
70
+ throw std::runtime_error(status.message);
71
+ }
72
+
73
+ auto jsiResult = createResult(rt, status, &results, metadata);
74
+ return jsiResult;
75
+ });
76
+ }
77
+
78
+ return {};
79
+ }
80
+
81
+ PreparedStatementHostObject::~PreparedStatementHostObject() {
82
+ if (_statement != NULL) {
83
+ sqlite3_finalize(_statement);
84
+ _statement = NULL;
85
+ }
86
+ }
87
+
88
+ } // namespace opsqlite
@@ -0,0 +1,36 @@
1
+ //
2
+ // PreparedStatementHostObject.hpp
3
+ // op-sqlite
4
+ //
5
+ // Created by Oscar Franco on 5/12/23.
6
+ //
7
+
8
+ #ifndef PreparedStatementHostObject_h
9
+ #define PreparedStatementHostObject_h
10
+
11
+ #include <jsi/jsi.h>
12
+ #include <memory>
13
+ #include <sqlite3.h>
14
+ #include <string>
15
+
16
+ namespace opsqlite {
17
+ namespace jsi = facebook::jsi;
18
+
19
+ class PreparedStatementHostObject : public jsi::HostObject {
20
+ public:
21
+ PreparedStatementHostObject(std::string dbName, sqlite3_stmt *statement);
22
+ virtual ~PreparedStatementHostObject();
23
+
24
+ std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt);
25
+
26
+ jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID);
27
+
28
+ private:
29
+ std::string _dbName;
30
+ // This shouldn't be de-allocated until sqlite3_finalize is called on it
31
+ sqlite3_stmt *_statement;
32
+ };
33
+
34
+ } // namespace opsqlite
35
+
36
+ #endif /* PreparedStatementHostObject_hpp */
@@ -0,0 +1,33 @@
1
+ #include "SmartHostObject.h"
2
+ #include "utils.h"
3
+
4
+ namespace opsqlite {
5
+
6
+ namespace jsi = facebook::jsi;
7
+
8
+ std::vector<jsi::PropNameID>
9
+ SmartHostObject::getPropertyNames(jsi::Runtime &rt) {
10
+ std::vector<jsi::PropNameID> keys;
11
+
12
+ for (auto field : fields) {
13
+ keys.push_back(jsi::PropNameID::forAscii(rt, field.first));
14
+ }
15
+
16
+ return keys;
17
+ }
18
+
19
+ jsi::Value SmartHostObject::get(jsi::Runtime &rt,
20
+ const jsi::PropNameID &propNameID) {
21
+ auto name = propNameID.utf8(rt);
22
+
23
+ for (auto field : fields) {
24
+ auto fieldName = field.first;
25
+ if (fieldName == name) {
26
+ return toJSI(rt, field.second);
27
+ }
28
+ }
29
+
30
+ return {};
31
+ }
32
+
33
+ } // namespace opsqlite
@@ -0,0 +1,26 @@
1
+ #ifndef SmartHostObject_h
2
+ #define SmartHostObject_h
3
+
4
+ #include "types.h"
5
+ #include <any>
6
+ #include <jsi/jsi.h>
7
+ #include <vector>
8
+
9
+ namespace opsqlite {
10
+
11
+ namespace jsi = facebook::jsi;
12
+
13
+ class JSI_EXPORT SmartHostObject : public jsi::HostObject {
14
+ public:
15
+ SmartHostObject(){};
16
+
17
+ std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime &rt);
18
+
19
+ jsi::Value get(jsi::Runtime &rt, const jsi::PropNameID &propNameID);
20
+
21
+ std::vector<std::pair<std::string, JSVariant>> fields;
22
+ };
23
+
24
+ } // namespace opsqlite
25
+
26
+ #endif /* SmartHostObject_h */