@op-engineering/op-sqlite 2.0.20 → 2.0.22

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
@@ -10,574 +10,9 @@ OP-SQLite embeds the latest version of SQLite and provides a low-level API to ex
10
10
 
11
11
  Created by [@ospfranco](https://twitter.com/ospfranco). **Please consider sponsoring!**.
12
12
 
13
- ## Benchmarks
13
+ # Docs
14
14
 
15
- You can find the [benchmarking code in the example app](https://github.com/OP-Engineering/op-sqlite/blob/main/example/src/Database.ts#L44). This is run using the `OP_SQLITE_PERF` [performance flag](#perf-flag) which in turns disables some old and unused features of sqlite to squeeze the last drop of performance.
16
-
17
- Memory consumption is minimised as much as possible since all the data is shared via HostObjects, meaning large queries can be run in few mbs.
18
-
19
- 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).
20
-
21
- Here is another benchmark with all the optimizations reading a single string value against react-native-mmkv.
22
-
23
- ![benchmark](benchmark2.png)
24
-
25
- # Encryption
26
-
27
- 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.
28
-
29
- # Database Location
30
-
31
- ## Default location
32
-
33
- If you don't pass a `location` the library creates/opens databases by appending the passed name plus, the [library directory on iOS](https://github.com/OP-Engineering/op-sqlite/blob/main/ios/OPSQLite.mm#L51) and the [database directory on Android](https://github.com/OP-Engineering/op-sqlite/blob/main/android/src/main/java/com/op/sqlite/OPSQLiteBridge.java#L18).
34
-
35
- ## Relative location
36
-
37
- You can use relative location to navigate in and out of the **default location**
38
-
39
- ```ts
40
- import { open } from '@op-engineering/op-sqlite';
41
-
42
- const db = open({
43
- name: 'myDB',
44
- location: '../files/databases',
45
- });
46
- ```
47
-
48
- Note that on iOS the file system is sand-boxed, so you cannot access files/directories outside your app bundle directories.
49
-
50
- ## Passing absolute paths
51
-
52
- You can also pass absolute paths to completely change the location of the database, the library exports useful paths you can use:
53
-
54
- ```ts
55
- import {
56
- IOS_LIBRARY_PATH, // Default iOS
57
- IOS_DOCUMENT_PATH,
58
- ANDROID_DATABASE_PATH, // Default Android
59
- ANDROID_FILES_PATH,
60
- ANDROID_EXTERNAL_FILES_PATH, // Android SD Card
61
- open,
62
- } from '@op-engineering/op-sqlite';
63
-
64
- const db = open({
65
- name: 'myDb',
66
- location: Platform.OS === 'ios' ? IOS_LIBRARY_PATH : ANDROID_DATABASE_PATH,
67
- });
68
- ```
69
-
70
- Here is an example if you want to access the SD card app's directory:
71
-
72
- ```ts
73
- const db = open({
74
- name: 'myDB',
75
- location:
76
- Platform.OS === 'ios' ? IOS_LIBRARY_PATH : ANDROID_EXTERNAL_FILES_PATH,
77
- });
78
- ```
79
-
80
- You can even drill down:
81
-
82
- ```ts
83
- const db = open({
84
- name: 'myDB',
85
- location:
86
- Platform.OS === 'ios'
87
- ? IOS_LIBRARY_PATH
88
- : `${ANDROID_EXTERNAL_FILES_PATH}/dbs/`,
89
- });
90
- ```
91
-
92
- ## In-memory
93
-
94
- Using SQLite in-memory mode is supported by passing a `':memory:'` as a location:
95
-
96
- ```ts
97
- import { open } from '@op-engineering/op-sqlite';
98
-
99
- const largeDb = open({
100
- name: 'inMemoryDb',
101
- location: ':memory:',
102
- });
103
- ```
104
-
105
- In memory databases are faster since they don't need to hit the disk I/O to save the data and are useful for synchronization only workflows.
106
-
107
- # Performance
108
-
109
- You can tweak SQLite to be faster (at the cost of some disadvantages). One possibility is [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 off memory mapping, any other number enables it with the cache size
119
- db.execute('PRAGMA mmap_size=268435456');
120
- ```
121
-
122
- You can also set journaling to memory (or even OFF if you are kinda crazy) to gain even more speed. Journaling is what allows SQLite to ROLLBACK statements and modifying it dangerous, so do it at your own risk
123
-
124
- ```ts
125
- db.execute('PRAGMA journal_mode = MEMORY;'); // or OFF
126
- ```
127
-
128
- If you use [prepared statements](#prepared-statements) are useful to reduce the time of critical queries.
129
-
130
- # Perf flags
131
-
132
- You can turn on the performance flag to tweak all possible performance enhancing compilation flags, this greatly affects performance of sqlite itself. Be aware this disables thread safety, you should only uses transactions (which operate based on a mutex in JS side) to avoid any issues.
133
-
134
- ```sh
135
- # For iOS install pods with the following env variable, you can also just an export like on Android
136
- # OP_SQLITE_PERF=1 npx pod-install
137
-
138
- # For Android you need to export the environment variable before running any command
139
- export OP_SQLITE_PERF=1
140
- # then any android build command will use the performance mode
141
- yarn android
142
- ```
143
-
144
- If correctly set you should see the following output in your console
145
-
146
- ```sh
147
- OP-SQLITE performance mode enabled! 🚀
148
- ```
149
-
150
- If you want to keep SQLite thread safety based on mutexes, you can use OP_SQLITE_PERF=2. This flag will enable the general compilation flags, except DSQLITE_THREADSAFE=0.
151
- If correctly set you should see the following output in your console
152
-
153
- ```sh
154
- OP-SQLITE (thread safe) performance mode enabled! 🚀
155
- ```
156
-
157
- Here you can read more about [SQLite Thread Safe](https://www.sqlite.org/threadsafe.html)
158
- # SQLite Gotchas
159
-
160
- ## Strictness
161
-
162
- It's important to notice SQLite unlike other databases by default does not strictly check for types, if you want true type safety when you declare your tables you need to use the `STRICT` keyword.
163
-
164
- ```ts
165
- db.execute('CREATE TABLE Test (
166
- id INT PRIMARY KEY,
167
- name TEXT
168
- ) STRICT;');
169
- ```
170
-
171
- If you don't set it, SQLite will happily write whatever you insert in your table, independtly of the declared type (it will try to cast it though, e.g. "1" string might be turned to 1 int).
172
-
173
- ## Foreign constraints
174
-
175
- When SQLite evaluates your query and you have forgein key constraints, it keeps track of the satisfied relations via a counter. Once your statement finishes executing and the counter is not 0, it throws a foreign key constraint failed error. Unfortunately, this simple design means it is impossible to catch which foreign constraint is failed and you will receive a generic error. Nothing op-sqlite can do about it, it's a design flaw in SQLite.
176
-
177
- In order to catch foreign key errors, you also need to execute the pragma when you open your connection:
178
-
179
- ```
180
- PRAGMA foreign_keys = true
181
- ```
182
-
183
- ## Error codes
184
-
185
- Sometimes you might be using valid SQL syntax for other engines or you might be doing something else wrong. The errors returned by op-sqlite contain the raw error code returned by SQLite and you should check [the reference](https://www.sqlite.org/rescode.html) for more detailed information.
186
-
187
- ## Quirks
188
-
189
- See the [full list of SQLite quirks](https://www.sqlite.org/quirks.html).
190
-
191
- # API
192
-
193
- ```typescript
194
- import {open} from '@op-engineering/op-sqlite'
195
-
196
- const db = open({name: 'myDb.sqlite'})
197
-
198
- // The db object contains the following methods:
199
- db = {
200
- close: () => void,
201
- delete: () => void,
202
- attach: (dbNameToAttach: string, alias: string, location?: string) => void,
203
- detach: (alias: string) => void,
204
- transaction: (fn: (tx: Transaction) => Promise<void>) => Promise<void>,
205
- execute: (query: string, params?: any[]) => QueryResult,
206
- executeAsync: (query: string, params?: any[]) => Promise<QueryResult>,
207
- executeBatch: (commands: SQLBatchTuple[]) => BatchQueryResult,
208
- executeBatchAsync: (commands: SQLBatchTuple[]) => Promise<BatchQueryResult>,
209
- loadFile: (location: string) => Promise<FileLoadResult>,
210
- updateHook: (
211
- callback: ((params: {
212
- table: string;
213
- operation: UpdateHookOperation;
214
- row?: any;
215
- rowId: number;
216
- }) => void) | null
217
- ) => void,
218
- commitHook: (callback: (() => void) | null) => void,
219
- rollbackHook: (callback: (() => void) | null) => void
220
- }
221
- ```
222
-
223
- ## Simple queries
224
-
225
- The basic query is **synchronous**, it will block rendering on large operations, further below you will find async versions.
226
-
227
- ```ts
228
- import { open } from '@op-engineering/op-sqlite';
229
-
230
- try {
231
- const db = open({ name: 'myDb.sqlite' });
232
-
233
- let { rows } = db.execute('SELECT somevalue FROM sometable');
234
-
235
- // _array internally holds the values, this is meant to comply with the webSQL spec
236
- rows._array.forEach((row) => {
237
- console.log(row);
238
- });
239
-
240
- let { rowsAffected } = await db.executeAsync(
241
- 'UPDATE sometable SET somecolumn = ? where somekey = ?',
242
- [0, 1]
243
- );
244
-
245
- console.log(`Update affected ${rowsAffected} rows`);
246
- } catch (e) {
247
- console.error('Something went wrong executing SQL commands:', e.message);
248
- }
249
- ```
250
-
251
- ## Multiple statements in a single string
252
-
253
- 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.
254
-
255
- ```ts
256
- // The result of this query will all be in a single array, no point in trying to read it
257
- db.execute(
258
- `CREATE TABLE T1 ( id INT PRIMARY KEY) STRICT;
259
- CREATE TABLE T2 ( id INT PRIMARY KEY) STRICT;`
260
- );
261
-
262
- let t1name = db.execute(
263
- "SELECT name FROM sqlite_master WHERE type='table' AND name='T1';"
264
- );
265
-
266
- console.log(t1name.rows?._array[0].name); // outputs "T1"
267
-
268
- let t2name = db.execute(
269
- "SELECT name FROM sqlite_master WHERE type='table' AND name='T2';"
270
- );
271
-
272
- console.log(t2name.rows?._array[0].name); // outputs "T2"
273
- ```
274
-
275
- ## Transactions
276
-
277
- Throwing an error inside the callback will ROLLBACK the transaction.
278
-
279
- If you want to execute a large set of commands as fast as possible you should use the `executeBatch` method, it wraps all the commands in a transaction and has less overhead.
280
-
281
- ```typescript
282
- await db.transaction('myDatabase', (tx) => {
283
- const { status } = tx.execute(
284
- 'UPDATE sometable SET somecolumn = ? where somekey = ?',
285
- [0, 1]
286
- );
287
-
288
- // offload from JS thread
289
- await tx.executeAsync = tx.executeAsync(
290
- 'UPDATE sometable SET somecolumn = ? where somekey = ?',
291
- [0, 1]
292
- );
293
-
294
- // Any uncatched error ROLLBACK transaction
295
- throw new Error('Random Error!');
296
-
297
- // You can manually commit or rollback
298
- tx.commit();
299
- // or
300
- tx.rollback();
301
- });
302
- ```
303
-
304
- ## Batch operation
305
-
306
- Batch execution allows the transactional execution of a set of commands
307
-
308
- ```typescript
309
- const commands = [
310
- ['CREATE TABLE TEST (id integer)'],
311
- ['INSERT INTO TEST (id) VALUES (?)', [1]],
312
- [('INSERT INTO TEST (id) VALUES (?)', [2])],
313
- [('INSERT INTO TEST (id) VALUES (?)', [[3], [4], [5], [6]])],
314
- ];
315
-
316
- const res = db.executeSqlBatch('myDatabase', commands);
317
-
318
- console.log(`Batch affected ${result.rowsAffected} rows`);
319
- ```
320
-
321
- ## Dynamic Column Metadata
322
-
323
- In some scenarios, dynamic applications may need to get some metadata information about the returned result set.
324
-
325
- This can be done by testing the returned data directly, but in some cases may not be enough, for example when data is stored outside
326
- SQLite datatypes. When fetching data directly from tables or views linked to table columns, SQLite can identify the table declared types:
327
-
328
- ```typescript
329
- let { metadata } = db.executeSql(
330
- 'myDatabase',
331
- 'SELECT int_column_1, bol_column_2 FROM sometable'
332
- );
333
-
334
- metadata.forEach((column) => {
335
- // Output:
336
- // int_column_1 - INTEGER
337
- // bol_column_2 - BOOLEAN
338
- console.log(`${column.name} - ${column.type}`);
339
- });
340
- ```
341
-
342
- ## Async operations
343
-
344
- 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.
345
-
346
- ```ts
347
- db.executeAsync('SELECT * FROM "User";', []).then(({ rows }) => {
348
- console.log('users', rows._array);
349
- });
350
- ```
351
-
352
- ## Blobs
353
-
354
- 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:
355
-
356
- ```ts
357
- db = open({
358
- name: 'blobs',
359
- });
360
-
361
- db.execute('DROP TABLE IF EXISTS BlobTable;');
362
- db.execute(
363
- 'CREATE TABLE BlobTable ( id INT PRIMARY KEY, name TEXT NOT NULL, content BLOB) STRICT;'
364
- );
365
-
366
- let binaryData = new Uint8Array(2);
367
- binaryData[0] = 42;
368
-
369
- db.execute(`INSERT OR REPLACE INTO BlobTable VALUES (?, ?, ?);`, [
370
- 1,
371
- 'myTestBlob',
372
- binaryData,
373
- ]);
374
-
375
- const result = db.execute('SELECT content FROM BlobTable');
376
-
377
- const finalUint8 = new Uint8Array(result.rows!._array[0].content);
378
- ```
379
-
380
- ## Prepared statements
381
-
382
- 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`:
383
-
384
- ```ts
385
- const statement = db.prepareStatement('SELECT * FROM User WHERE name = ?;');
386
- statement.bind(['Oscar']);
387
- let results1 = statement.execute();
388
-
389
- statement.bind(['Carlos']);
390
- let results2 = statement.execute();
391
- ```
392
-
393
- You only pay the price of parsing the query once, and each subsequent execution should be faster.
394
-
395
- # Raw execution
396
-
397
- If you don't care about the keys you can use a simplified execution that will return an array of results.
398
-
399
- ```ts
400
- let result = await db.executeRawAsync('SELECT * FROM Users;');
401
- // result = [[123, 'Katie', ...]]
402
- ```
403
-
404
- # Attach or Detach other databases
405
-
406
- SQLite supports attaching or detaching other database files into your main database connection through an alias.
407
- You can do any operation you like on this attached database like JOIN results across tables in different schemas, or update data or objects.
408
- These databases can have different configurations, like journal modes, and cache settings.
409
-
410
- You can, at any moment, detach a database that you don't need anymore. You don't need to detach an attached database before closing your connection. Closing the main connection will detach any attached databases.
411
-
412
- SQLite has a limit for attached databases: A default of 10, and a global max of 125
413
-
414
- References: [Attach](https://www.sqlite.org/lang_attach.html) - [Detach](https://www.sqlite.org/lang_detach.html)
415
-
416
- ```ts
417
- db.attach('mainDatabase', 'statistics', 'stats', '../databases');
418
-
419
- const res = db.execute(
420
- 'mainDatabase',
421
- 'SELECT * FROM some_table_from_mainschema a INNER JOIN stats.some_table b on a.id_column = b.id_column'
422
- );
423
-
424
- // You can detach databases at any moment
425
- db.detach('mainDatabase', 'stats');
426
- if (!detachResult.status) {
427
- // Database de-attached
428
- }
429
- ```
430
-
431
- # Loading SQL Dump Files
432
-
433
- If you have a SQL dump file, you can load it directly, with low memory consumption:
434
-
435
- ```typescript
436
- const { rowsAffected, commands } = db
437
- .loadFile('myDatabase', '/absolute/path/to/file.sql')
438
- .then((res) => {
439
- const { rowsAffected, commands } = res;
440
- });
441
- ```
442
-
443
- # Hooks
444
-
445
- You can subscribe to changes in your database by using an update hook:
446
-
447
- ```ts
448
- // Bear in mind: rowId is not your table primary key but the internal rowId sqlite uses
449
- // to keep track of the table rows
450
- db.updateHook(({ rowId, table, operation, row = {} }) => {
451
- console.warn(`Hook has been called, rowId: ${rowId}, ${table}, ${operation}`);
452
- // Will contain the entire row that changed
453
- // only on UPDATE and INSERT operations
454
- console.warn(JSON.stringify(row, null, 2));
455
- });
456
-
457
- db.execute('INSERT INTO "User" (id, name, age, networth) VALUES(?, ?, ?, ?)', [
458
- id,
459
- name,
460
- age,
461
- networth,
462
- ]);
463
- ```
464
-
465
- Same goes for commit and rollback hooks
466
-
467
- ```ts
468
- // will fire whenever a transaction commits
469
- db.commitHook(() => {
470
- console.log('Transaction commmitted!');
471
- });
472
-
473
- db.rollbackHook(() => {
474
- console.log('Transaction rolled back!');
475
- });
476
-
477
- // will fire the commit hook
478
- db.transaction(async (tx) => {
479
- tx.execute(
480
- 'INSERT INTO "User" (id, name, age, networth) VALUES(?, ?, ?, ?)',
481
- [id, name, age, networth]
482
- );
483
- });
484
-
485
- // will fire the rollback hook
486
- try {
487
- await db.transaction(async (tx) => {
488
- throw new Error('Test Error');
489
- });
490
- } catch (e) {
491
- // intentionally left blank
492
- }
493
- ```
494
-
495
- You can pass `null`` to remove hooks at any moment:
496
-
497
- ```ts
498
- db.updateHook(null);
499
-
500
- db.commitHook(null);
501
-
502
- db.rollbackHook(null);
503
- ```
504
-
505
- # Use built-in SQLite
506
-
507
- On iOS you can use the embedded SQLite, when running `pod-install` add an environment flag:
508
-
509
- ```
510
- OP_SQLITE_USE_PHONE_VERSION=1 npx pod-install
511
- ```
512
-
513
- 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. Take note that the embedded version of SQLite does not support loading runtime extensions due to security concerns from apple.
514
-
515
- # Enable compile-time options
516
-
517
- By specifying pre-processor flags, you can enable optional features like FTS5, Geopoly, etc.
518
-
519
- ## iOS
520
-
521
- Add a `post_install` block to your `<PROJECT_ROOT>/ios/Podfile` like so:
522
-
523
- ```ruby
524
- post_install do |installer|
525
- installer.pods_project.targets.each do |target|
526
- if target.name == "op-sqlite" then
527
- target.build_configurations.each do |config|
528
- config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] << 'SQLITE_ENABLE_FTS5=1'
529
- end
530
- end
531
- end
532
- end
533
- ```
534
-
535
- Replace the `<SQLITE_FLAGS>` part with the flags you want to add.
536
- For example, you could add `SQLITE_ENABLE_FTS5=1` to `GCC_PREPROCESSOR_DEFINITIONS` to enable FTS5 in the iOS project.
537
-
538
- ## Android
539
-
540
- You can specify flags via `<PROJECT_ROOT>/android/gradle.properties` like so:
541
-
542
- ```
543
- OPSQLiteFlags="-DSQLITE_ENABLE_FTS5=1"
544
- ```
545
-
546
- # Runtime loadable extensions
547
-
548
- You can load your own extensions on runtime.
549
-
550
- ```ts
551
- db.loadExtension('/path/to/library.so', 'optional_entry_point_function_name');
552
- ```
553
-
554
- You will need to compile your extension for both iOS and Android and all the respective architectures and make it available in a location the library can read (be careful about sandboxing).
555
-
556
- # Additional configuration
557
-
558
- ## App groups (iOS only)
559
-
560
- 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.
561
-
562
- 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)
563
-
564
- ## use_frameworks
565
-
566
- If you are using `use_frameworks` (because one of your dependencies requires e.g. firebase), you might get a linking error since the OS version will try to be linked instead of the version compiled directly from sources. In order to get around this you can add the following into your Podfile:
567
-
568
- ```ruby
569
- pre_install do |installer|
570
- installer.pod_targets.each do |pod|
571
- if pod.name.eql?('op-sqlite')
572
- def pod.build_type
573
- Pod::BuildType.static_library
574
- end
575
- end
576
- end
577
- end
578
- ```
579
-
580
- It will change the type of library only for op-sqlite only to static. This should be fine as SQLite is compiled from sources, so no missing symbols should be there.
15
+ [See the docs](https://ospfranco.notion.site/OP-SQLite-Documentation-a279a52102464d0cb13c3fa230d2f2dc?pvs=4)
581
16
 
582
17
  # Contribute
583
18
 
@@ -1,10 +1,3 @@
1
- //
2
- // PreparedStatementHostObject.cpp
3
- // op-sqlite
4
- //
5
- // Created by Oscar Franco on 5/12/23.
6
- //
7
-
8
1
  #include "PreparedStatementHostObject.h"
9
2
  #include "bridge.h"
10
3
  #include "macros.h"
@@ -31,7 +24,7 @@ jsi::Value PreparedStatementHostObject::get(jsi::Runtime &rt,
31
24
 
32
25
  if (name == "bind") {
33
26
  return HOSTFN("bind", 1) {
34
- if (_statement == NULL) {
27
+ if (_statement == nullptr) {
35
28
  throw std::runtime_error("statement has been freed");
36
29
  }
37
30
 
@@ -46,7 +39,7 @@ jsi::Value PreparedStatementHostObject::get(jsi::Runtime &rt,
46
39
 
47
40
  if (name == "execute") {
48
41
  return HOSTFN("execute", 1) {
49
- if (_statement == NULL) {
42
+ if (_statement == nullptr) {
50
43
  throw std::runtime_error("statement has been freed");
51
44
  }
52
45
  std::vector<DumbHostObject> results;
@@ -69,9 +62,9 @@ jsi::Value PreparedStatementHostObject::get(jsi::Runtime &rt,
69
62
  }
70
63
 
71
64
  PreparedStatementHostObject::~PreparedStatementHostObject() {
72
- if (_statement != NULL) {
65
+ if (_statement != nullptr) {
73
66
  sqlite3_finalize(_statement);
74
- _statement = NULL;
67
+ _statement = nullptr;
75
68
  }
76
69
  }
77
70
 
package/cpp/bridge.cpp CHANGED
@@ -80,14 +80,11 @@ BridgeResult opsqlite_attach(std::string const &mainDBName,
80
80
  std::string const &docPath,
81
81
  std::string const &databaseToAttach,
82
82
  std::string const &alias) {
83
- /**
84
- * There is no need to check if mainDBName is opened because
85
- * sqliteExecuteLiteral will do that.
86
- * */
87
83
  std::string dbPath = get_db_path(databaseToAttach, docPath);
88
84
  std::string statement = "ATTACH DATABASE '" + dbPath + "' AS " + alias;
89
85
 
90
- BridgeResult result = opsqlite_execute_literal(mainDBName, statement);
86
+ BridgeResult result =
87
+ opsqlite_execute(mainDBName, statement, nullptr, nullptr, nullptr);
91
88
 
92
89
  if (result.type == SQLiteError) {
93
90
  return {
@@ -103,12 +100,9 @@ BridgeResult opsqlite_attach(std::string const &mainDBName,
103
100
 
104
101
  BridgeResult opsqlite_detach(std::string const &mainDBName,
105
102
  std::string const &alias) {
106
- /**
107
- * There is no need to check if mainDBName is opened because
108
- * sqliteExecuteLiteral will do that.
109
- * */
110
103
  std::string statement = "DETACH DATABASE " + alias;
111
- BridgeResult result = opsqlite_execute_literal(mainDBName, statement);
104
+ BridgeResult result =
105
+ opsqlite_execute(mainDBName, statement, nullptr, nullptr, nullptr);
112
106
  if (result.type == SQLiteError) {
113
107
  return BridgeResult{
114
108
  .type = SQLiteError,
@@ -526,7 +520,7 @@ opsqlite_execute_raw(std::string const &dbName, std::string const &query,
526
520
  bool isConsuming = true;
527
521
  bool isFailed = false;
528
522
 
529
- int result = SQLITE_OK;
523
+ int step = SQLITE_OK;
530
524
 
531
525
  do {
532
526
  const char *queryStr =
@@ -562,13 +556,14 @@ opsqlite_execute_raw(std::string const &dbName, std::string const &query,
562
556
  std::string column_name, column_declared_type;
563
557
 
564
558
  while (isConsuming) {
565
- result = sqlite3_step(statement);
559
+ step = sqlite3_step(statement);
566
560
 
567
- switch (result) {
561
+ switch (step) {
568
562
  case SQLITE_ROW: {
569
- if (results == NULL) {
563
+ if (results == nullptr) {
570
564
  break;
571
565
  }
566
+
572
567
  std::vector<JSVariant> row;
573
568
 
574
569
  i = 0;
@@ -616,7 +611,8 @@ opsqlite_execute_raw(std::string const &dbName, std::string const &query,
616
611
  }
617
612
 
618
613
  case SQLITE_NULL:
619
- // Intentionally left blank
614
+ row.push_back(JSVariant(nullptr));
615
+ break;
620
616
 
621
617
  default:
622
618
  row.push_back(JSVariant(nullptr));
@@ -649,7 +645,7 @@ opsqlite_execute_raw(std::string const &dbName, std::string const &query,
649
645
 
650
646
  return {.type = SQLiteError,
651
647
  .message =
652
- "[op-sqlite] SQLite error code: " + std::to_string(result) +
648
+ "[op-sqlite] SQLite error code: " + std::to_string(step) +
653
649
  ", description: " + std::string(errorMessage) +
654
650
  ".\nSee SQLite error codes reference: "
655
651
  "https://www.sqlite.org/rescode.html"};
@@ -663,60 +659,6 @@ opsqlite_execute_raw(std::string const &dbName, std::string const &query,
663
659
  .insertId = static_cast<double>(latestInsertRowId)};
664
660
  }
665
661
 
666
- /// Executes without returning any results, Useful for performance critical
667
- /// operations
668
- BridgeResult opsqlite_execute_literal(std::string const &dbName,
669
- std::string const &query) {
670
- check_db_open(dbName);
671
-
672
- sqlite3 *db = dbMap[dbName];
673
- sqlite3_stmt *statement;
674
-
675
- int statementStatus =
676
- sqlite3_prepare_v2(db, query.c_str(), -1, &statement, NULL);
677
-
678
- if (statementStatus != SQLITE_OK) {
679
- const char *message = sqlite3_errmsg(db);
680
- return {SQLiteError,
681
- "[op-sqlite] SQL statement error: " + std::string(message), 0};
682
- }
683
-
684
- bool isConsuming = true;
685
- bool isFailed = false;
686
-
687
- int result;
688
- std::string column_name;
689
-
690
- while (isConsuming) {
691
- result = sqlite3_step(statement);
692
-
693
- switch (result) {
694
- case SQLITE_ROW:
695
- isConsuming = true;
696
- break;
697
-
698
- case SQLITE_DONE:
699
- isConsuming = false;
700
- break;
701
-
702
- default:
703
- isFailed = true;
704
- isConsuming = false;
705
- }
706
- }
707
-
708
- sqlite3_finalize(statement);
709
-
710
- if (isFailed) {
711
- const char *message = sqlite3_errmsg(db);
712
- return {SQLiteError,
713
- "[op-sqlite] SQL execution error: " + std::string(message), 0};
714
- }
715
-
716
- int changedRowCount = sqlite3_changes(db);
717
- return {SQLiteOk, "", changedRowCount};
718
- }
719
-
720
662
  void opsqlite_close_all() {
721
663
  for (auto const &x : dbMap) {
722
664
  // Interrupt will make all pending operations to fail with SQLITE_INTERRUPT
package/cpp/bridge.h CHANGED
@@ -46,9 +46,6 @@ BridgeResult opsqlite_execute_raw(std::string const &dbName,
46
46
  const std::vector<JSVariant> *params,
47
47
  std::vector<std::vector<JSVariant>> *results);
48
48
 
49
- BridgeResult opsqlite_execute_literal(std::string const &dbName,
50
- std::string const &query);
51
-
52
49
  void opsqlite_close_all();
53
50
 
54
51
  BridgeResult opsqlite_register_update_hook(std::string const &dbName,
@@ -57,7 +57,8 @@ BatchResult sqliteExecuteBatch(std::string dbName,
57
57
 
58
58
  try {
59
59
  int affectedRows = 0;
60
- opsqlite_execute_literal(dbName, "BEGIN EXCLUSIVE TRANSACTION");
60
+ opsqlite_execute(dbName, "BEGIN EXCLUSIVE TRANSACTION", nullptr, nullptr,
61
+ nullptr);
61
62
  for (int i = 0; i < commandCount; i++) {
62
63
  auto command = commands->at(i);
63
64
  // We do not provide a datastructure to receive query data because we
@@ -65,7 +66,7 @@ BatchResult sqliteExecuteBatch(std::string dbName,
65
66
  auto result = opsqlite_execute(dbName, command.sql, command.params.get(),
66
67
  nullptr, nullptr);
67
68
  if (result.type == SQLiteError) {
68
- opsqlite_execute_literal(dbName, "ROLLBACK");
69
+ opsqlite_execute(dbName, "ROLLBACK", nullptr, nullptr, nullptr);
69
70
  return BatchResult{
70
71
  .type = SQLiteError,
71
72
  .message = result.message,
@@ -74,14 +75,14 @@ BatchResult sqliteExecuteBatch(std::string dbName,
74
75
  affectedRows += result.affectedRows;
75
76
  }
76
77
  }
77
- opsqlite_execute_literal(dbName, "COMMIT");
78
+ opsqlite_execute(dbName, "COMMIT", nullptr, nullptr, nullptr);
78
79
  return BatchResult{
79
80
  .type = SQLiteOk,
80
81
  .affectedRows = affectedRows,
81
82
  .commands = static_cast<int>(commandCount),
82
83
  };
83
84
  } catch (std::exception &exc) {
84
- opsqlite_execute_literal(dbName, "ROLLBACK");
85
+ opsqlite_execute(dbName, "ROLLBACK", nullptr, nullptr, nullptr);
85
86
  return BatchResult{
86
87
  .type = SQLiteError,
87
88
  .message = exc.what(),
package/cpp/types.h CHANGED
@@ -2,8 +2,25 @@
2
2
  #define types_h
3
3
 
4
4
  #include <memory>
5
+ #include <string>
5
6
  #include <variant>
6
7
 
8
+ enum ResultType { SQLiteOk, SQLiteError };
9
+
10
+ struct BridgeResult {
11
+ ResultType type;
12
+ std::string message;
13
+ int affectedRows;
14
+ double insertId;
15
+ };
16
+
17
+ struct BatchResult {
18
+ ResultType type;
19
+ std::string message;
20
+ int affectedRows;
21
+ int commands;
22
+ };
23
+
7
24
  struct ArrayBuffer {
8
25
  std::shared_ptr<uint8_t> data;
9
26
  size_t size;
package/cpp/utils.cpp CHANGED
@@ -30,10 +30,6 @@ jsi::Value toJSI(jsi::Runtime &rt, JSVariant value) {
30
30
  jsi::Object o = array_buffer_ctor.callAsConstructor(rt, (int)jsBuffer.size)
31
31
  .getObject(rt);
32
32
  jsi::ArrayBuffer buf = o.getArrayBuffer(rt);
33
- // You cannot share raw memory between native and JS
34
- // always copy the data
35
- // see https://github.com/facebook/hermes/pull/419 and
36
- // https://github.com/facebook/hermes/issues/564.
37
33
  memcpy(buf.data(rt), jsBuffer.data.get(), jsBuffer.size);
38
34
  return o;
39
35
  }
@@ -62,25 +58,24 @@ JSVariant toVariant(jsi::Runtime &rt, const jsi::Value &value) {
62
58
  return JSVariant(strVal);
63
59
  } else if (value.isObject()) {
64
60
  auto obj = value.asObject(rt);
65
- if (obj.isArrayBuffer(rt)) {
66
- auto buffer = obj.getArrayBuffer(rt);
67
61
 
68
- uint8_t *data = new uint8_t[buffer.size(rt)];
69
- // You cannot share raw memory between native and JS
70
- // always copy the data
71
- // see https://github.com/facebook/hermes/pull/419 and
72
- // https://github.com/facebook/hermes/issues/564.
73
- memcpy(data, buffer.data(rt), buffer.size(rt));
74
-
75
- return JSVariant(ArrayBuffer{.data = std::shared_ptr<uint8_t>{data},
76
- .size = buffer.size(rt)});
77
- } else {
62
+ if (!obj.isArrayBuffer(rt)) {
78
63
  throw std::invalid_argument(
79
- "Unknown JSI ArrayBuffer to variant value conversion, received "
80
- "object instead of ArrayBuffer");
64
+ "Objects returned by OP-SQLite, are C++ HostObjects and thus cannot "
65
+ "store any object, only scalar "
66
+ "properties (int, long, double, string, bool) and ArrayBuffers.");
81
67
  }
68
+
69
+ auto buffer = obj.getArrayBuffer(rt);
70
+ uint8_t *data = new uint8_t[buffer.size(rt)];
71
+ memcpy(data, buffer.data(rt), buffer.size(rt));
72
+
73
+ return JSVariant(ArrayBuffer{.data = std::shared_ptr<uint8_t>{data},
74
+ .size = buffer.size(rt)});
75
+
82
76
  } else {
83
- throw std::invalid_argument("Unknown JSI to variant value conversion");
77
+ throw std::invalid_argument(
78
+ "Cannot convert JSI value to C++ Variant value");
84
79
  }
85
80
  }
86
81
 
@@ -210,12 +205,14 @@ BatchResult importSQLFile(std::string dbName, std::string fileLocation) {
210
205
  try {
211
206
  int affectedRows = 0;
212
207
  int commands = 0;
213
- opsqlite_execute_literal(dbName, "BEGIN EXCLUSIVE TRANSACTION");
208
+ opsqlite_execute(dbName, "BEGIN EXCLUSIVE TRANSACTION", nullptr, nullptr,
209
+ nullptr);
214
210
  while (std::getline(sqFile, line, '\n')) {
215
211
  if (!line.empty()) {
216
- BridgeResult result = opsqlite_execute_literal(dbName, line);
212
+ BridgeResult result =
213
+ opsqlite_execute(dbName, line, nullptr, nullptr, nullptr);
217
214
  if (result.type == SQLiteError) {
218
- opsqlite_execute_literal(dbName, "ROLLBACK");
215
+ opsqlite_execute(dbName, "ROLLBACK", nullptr, nullptr, nullptr);
219
216
  sqFile.close();
220
217
  return {SQLiteError, result.message, 0, commands};
221
218
  } else {
@@ -225,11 +222,11 @@ BatchResult importSQLFile(std::string dbName, std::string fileLocation) {
225
222
  }
226
223
  }
227
224
  sqFile.close();
228
- opsqlite_execute_literal(dbName, "COMMIT");
225
+ opsqlite_execute(dbName, "COMMIT", nullptr, nullptr, nullptr);
229
226
  return {SQLiteOk, "", affectedRows, commands};
230
227
  } catch (...) {
231
228
  sqFile.close();
232
- opsqlite_execute_literal(dbName, "ROLLBACK");
229
+ opsqlite_execute(dbName, "ROLLBACK", nullptr, nullptr, nullptr);
233
230
  return {SQLiteError,
234
231
  "[op-sqlite][loadSQLFile] Unexpected error, transaction was "
235
232
  "rolledback",
package/cpp/utils.h CHANGED
@@ -15,22 +15,6 @@ namespace opsqlite {
15
15
 
16
16
  namespace jsi = facebook::jsi;
17
17
 
18
- enum ResultType { SQLiteOk, SQLiteError };
19
-
20
- struct BridgeResult {
21
- ResultType type;
22
- std::string message;
23
- int affectedRows;
24
- double insertId;
25
- };
26
-
27
- struct BatchResult {
28
- ResultType type;
29
- std::string message;
30
- int affectedRows;
31
- int commands;
32
- };
33
-
34
18
  jsi::Value toJSI(jsi::Runtime &rt, JSVariant value);
35
19
 
36
20
  JSVariant toVariant(jsi::Runtime &rt, jsi::Value const &value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@op-engineering/op-sqlite",
3
- "version": "2.0.20",
3
+ "version": "2.0.22",
4
4
  "description": "Next generation SQLite for React Native",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -23,7 +23,7 @@
23
23
  "!**/__mocks__"
24
24
  ],
25
25
  "scripts": {
26
- "typescript": "tsc --noEmit",
26
+ "typecheck": "tsc --noEmit",
27
27
  "prepare": "bob build",
28
28
  "example": "yarn --cwd example",
29
29
  "pods": "cd example && yarn pods",