@op-engineering/op-sqlite 1.0.10 → 1.0.12

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/cpp/bridge.cpp CHANGED
@@ -13,279 +13,433 @@
13
13
 
14
14
  namespace opsqlite {
15
15
 
16
- std::unordered_map<std::string, sqlite3 *> dbMap = std::unordered_map<std::string, sqlite3 *>();
16
+ std::unordered_map<std::string, sqlite3 *> dbMap = std::unordered_map<std::string, sqlite3 *>();
17
+ std::unordered_map<
18
+ std::string,
19
+ std::function<void (std::string dbName, std::string tableName, std::string operation, int rowId)>> callbackMap =
20
+ std::unordered_map<
21
+ std::string,
22
+ std::function<void (std::string dbName, std::string tableName, std::string operation, int rowId)>>();
17
23
 
18
- bool folder_exists(const std::string &foldername)
19
- {
20
- struct stat buffer;
21
- return (stat(foldername.c_str(), &buffer) == 0);
22
- }
23
-
24
- /**
25
- * Portable wrapper for mkdir. Internally used by mkdir()
26
- * @param[in] path the full path of the directory to create.
27
- * @return zero on success, otherwise -1.
28
- */
29
- int _mkdir(const char *path)
30
- {
31
- #if _POSIX_C_SOURCE
32
- return mkdir(path);
33
- #else
34
- return mkdir(path, 0755); // not sure if this works on mac
35
- #endif
36
- }
37
-
38
- /**
39
- * Recursive, portable wrapper for mkdir.
40
- * @param[in] path the full path of the directory to create.
41
- * @return zero on success, otherwise -1.
42
- */
43
- int mkdir(const char *path)
44
- {
45
- std::string current_level = "/";
46
- std::string level;
47
- std::stringstream ss(path);
48
- // First line is empty because it starts with /User
49
- getline(ss, level, '/');
50
- // split path using slash as a separator
51
- while (getline(ss, level, '/'))
24
+ bool folder_exists(const std::string &foldername)
52
25
  {
53
- current_level += level; // append folder to the current level
54
- // create current level
55
- if (!folder_exists(current_level) && _mkdir(current_level.c_str()) != 0)
56
- return -1;
57
-
58
- current_level += "/"; // don't forget to append a slash
26
+ struct stat buffer;
27
+ return (stat(foldername.c_str(), &buffer) == 0);
59
28
  }
60
-
61
- return 0;
62
- }
63
-
64
- inline bool file_exists(const std::string &path)
65
- {
66
- struct stat buffer;
67
- return (stat(path.c_str(), &buffer) == 0);
68
- }
69
29
 
70
- std::string get_db_path(std::string const dbName, std::string const docPath)
71
- {
72
- mkdir(docPath.c_str());
73
- return docPath + "/" + dbName;
74
- }
75
-
76
- BridgeResult sqliteOpenDb(std::string const dbName, std::string const docPath, bool memoryStorage)
77
- {
78
- std::string dbPath = memoryStorage ? ":memory:" : get_db_path(dbName, docPath);
79
-
80
- int sqlOpenFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
81
-
82
- sqlite3 *db;
83
- int exit = 0;
84
- exit = sqlite3_open_v2(dbPath.c_str(), &db, sqlOpenFlags, nullptr);
85
-
86
- if (exit != SQLITE_OK)
30
+ /**
31
+ * Portable wrapper for mkdir. Internally used by mkdir()
32
+ * @param[in] path the full path of the directory to create.
33
+ * @return zero on success, otherwise -1.
34
+ */
35
+ int _mkdir(const char *path)
87
36
  {
88
- return {
89
- .type = SQLiteError,
90
- .message = sqlite3_errmsg(db)
91
- };
37
+ #if _POSIX_C_SOURCE
38
+ return mkdir(path);
39
+ #else
40
+ return mkdir(path, 0755); // not sure if this works on mac
41
+ #endif
92
42
  }
93
- else
43
+
44
+ /**
45
+ * Recursive, portable wrapper for mkdir.
46
+ * @param[in] path the full path of the directory to create.
47
+ * @return zero on success, otherwise -1.
48
+ */
49
+ int mkdir(const char *path)
94
50
  {
95
- dbMap[dbName] = db;
51
+ std::string current_level = "/";
52
+ std::string level;
53
+ std::stringstream ss(path);
54
+ // First line is empty because it starts with /User
55
+ getline(ss, level, '/');
56
+ // split path using slash as a separator
57
+ while (getline(ss, level, '/'))
58
+ {
59
+ current_level += level; // append folder to the current level
60
+ // create current level
61
+ if (!folder_exists(current_level) && _mkdir(current_level.c_str()) != 0)
62
+ return -1;
63
+
64
+ current_level += "/"; // don't forget to append a slash
65
+ }
66
+
67
+ return 0;
96
68
  }
97
-
98
- return BridgeResult{
99
- .type = SQLiteOk,
100
- .affectedRows = 0
101
- };
102
- }
103
69
 
104
- BridgeResult sqliteCloseDb(std::string const dbName)
105
- {
106
-
107
- if (dbMap.count(dbName) == 0)
70
+ inline bool file_exists(const std::string &path)
108
71
  {
109
- return {
110
- .type = SQLiteError,
111
- .message = dbName + " is not open",
112
- };
72
+ struct stat buffer;
73
+ return (stat(path.c_str(), &buffer) == 0);
113
74
  }
114
-
115
- sqlite3 *db = dbMap[dbName];
116
-
117
- sqlite3_close_v2(db);
118
-
119
- dbMap.erase(dbName);
120
-
121
- return BridgeResult{
122
- .type = SQLiteOk,
123
- };
124
- }
125
75
 
126
- void sqliteCloseAll() {
127
- for (auto const& x : dbMap) {
128
- // In certain cases, this will return SQLITE_OK, mark the database connection as an unusable "zombie",
129
- // and deallocate the connection later.
130
- sqlite3_close_v2(x.second);
76
+ std::string get_db_path(std::string const dbName, std::string const docPath)
77
+ {
78
+ mkdir(docPath.c_str());
79
+ return docPath + "/" + dbName;
131
80
  }
132
- dbMap.clear();
133
- }
134
81
 
135
- BridgeResult sqliteAttachDb(std::string const mainDBName, std::string const docPath, std::string const databaseToAttach, std::string const alias)
136
- {
137
- /**
138
- * There is no need to check if mainDBName is opened because sqliteExecuteLiteral will do that.
139
- * */
140
- std::string dbPath = get_db_path(databaseToAttach, docPath);
141
- std::string statement = "ATTACH DATABASE '" + dbPath + "' AS " + alias;
142
-
143
- BridgeResult result = sqliteExecuteLiteral(mainDBName, statement);
144
-
145
- if (result.type == SQLiteError)
82
+ BridgeResult sqliteOpenDb(std::string const dbName, std::string const docPath, bool memoryStorage)
146
83
  {
147
- return {
148
- .type = SQLiteError,
149
- .message = mainDBName + " was unable to attach another database: " + std::string(result.message),
84
+ std::string dbPath = memoryStorage ? ":memory:" : get_db_path(dbName, docPath);
85
+
86
+ int sqlOpenFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX;
87
+
88
+ sqlite3 *db;
89
+ int exit = 0;
90
+ exit = sqlite3_open_v2(dbPath.c_str(), &db, sqlOpenFlags, nullptr);
91
+
92
+ if (exit != SQLITE_OK)
93
+ {
94
+ return {
95
+ .type = SQLiteError,
96
+ .message = sqlite3_errmsg(db)
97
+ };
98
+ }
99
+
100
+ dbMap[dbName] = db;
101
+
102
+ return BridgeResult{
103
+ .type = SQLiteOk,
104
+ .affectedRows = 0
150
105
  };
151
106
  }
152
- return {
153
- .type = SQLiteOk,
154
- };
155
- }
156
107
 
157
- BridgeResult sqliteDetachDb(std::string const mainDBName, std::string const alias)
158
- {
159
- /**
160
- * There is no need to check if mainDBName is opened because sqliteExecuteLiteral will do that.
161
- * */
162
- std::string statement = "DETACH DATABASE " + alias;
163
- BridgeResult result = sqliteExecuteLiteral(mainDBName, statement);
164
- if (result.type == SQLiteError)
108
+ BridgeResult sqliteCloseDb(std::string const dbName)
165
109
  {
110
+
111
+ if (dbMap.count(dbName) == 0)
112
+ {
113
+ return {
114
+ .type = SQLiteError,
115
+ .message = dbName + " is not open",
116
+ };
117
+ }
118
+
119
+ sqlite3 *db = dbMap[dbName];
120
+
121
+ sqlite3_close_v2(db);
122
+
123
+ dbMap.erase(dbName);
124
+
166
125
  return BridgeResult{
167
- .type = SQLiteError,
168
- .message = mainDBName + "was unable to detach database: " + std::string(result.message),
126
+ .type = SQLiteOk,
169
127
  };
170
128
  }
171
- return BridgeResult{
172
- .type = SQLiteOk,
173
- };
174
- }
175
129
 
176
- BridgeResult sqliteRemoveDb(std::string const dbName, std::string const docPath)
177
- {
178
- if (dbMap.count(dbName) == 1)
130
+ BridgeResult sqliteAttachDb(std::string const mainDBName, std::string const docPath, std::string const databaseToAttach, std::string const alias)
179
131
  {
180
- BridgeResult closeResult = sqliteCloseDb(dbName);
181
- if (closeResult.type == SQLiteError)
132
+ /**
133
+ * There is no need to check if mainDBName is opened because sqliteExecuteLiteral will do that.
134
+ * */
135
+ std::string dbPath = get_db_path(databaseToAttach, docPath);
136
+ std::string statement = "ATTACH DATABASE '" + dbPath + "' AS " + alias;
137
+
138
+ BridgeResult result = sqliteExecuteLiteral(mainDBName, statement);
139
+
140
+ if (result.type == SQLiteError)
182
141
  {
183
- return closeResult;
142
+ return {
143
+ .type = SQLiteError,
144
+ .message = mainDBName + " was unable to attach another database: " + std::string(result.message),
145
+ };
184
146
  }
185
- }
186
-
187
- std::string dbPath = get_db_path(dbName, docPath);
188
-
189
- if (!file_exists(dbPath))
190
- {
191
147
  return {
192
- .type = SQLiteError,
193
- .message = "[op-sqlite]: Database file not found" + dbPath
148
+ .type = SQLiteOk,
194
149
  };
195
150
  }
196
-
197
- remove(dbPath.c_str());
198
-
199
- return {
200
- .type = SQLiteOk,
201
- };
202
- }
203
151
 
204
- inline void bindStatement(sqlite3_stmt *statement, std::vector<JSVariant> *values)
205
- {
206
- size_t size = values->size();
207
-
208
- for (int ii = 0; ii < size; ii++)
152
+ BridgeResult sqliteDetachDb(std::string const mainDBName, std::string const alias)
209
153
  {
210
- int sqIndex = ii + 1;
211
- JSVariant value = values->at(ii);
212
-
213
- if (std::holds_alternative<bool>(value))
154
+ /**
155
+ * There is no need to check if mainDBName is opened because sqliteExecuteLiteral will do that.
156
+ * */
157
+ std::string statement = "DETACH DATABASE " + alias;
158
+ BridgeResult result = sqliteExecuteLiteral(mainDBName, statement);
159
+ if (result.type == SQLiteError)
214
160
  {
215
- sqlite3_bind_int(statement, sqIndex, std::get<bool>(value));
161
+ return BridgeResult{
162
+ .type = SQLiteError,
163
+ .message = mainDBName + "was unable to detach database: " + std::string(result.message),
164
+ };
216
165
  }
217
- else if (std::holds_alternative<int>(value))
166
+ return BridgeResult{
167
+ .type = SQLiteOk,
168
+ };
169
+ }
170
+
171
+ BridgeResult sqliteRemoveDb(std::string const dbName, std::string const docPath)
172
+ {
173
+ if (dbMap.count(dbName) == 1)
218
174
  {
219
- sqlite3_bind_int(statement, sqIndex, std::get<int>(value));
175
+ BridgeResult closeResult = sqliteCloseDb(dbName);
176
+ if (closeResult.type == SQLiteError)
177
+ {
178
+ return closeResult;
179
+ }
220
180
  }
221
- else if (std::holds_alternative<long long>(value))
181
+
182
+ std::string dbPath = get_db_path(dbName, docPath);
183
+
184
+ if (!file_exists(dbPath))
222
185
  {
223
- sqlite3_bind_double(statement, sqIndex, std::get<long long>(value));
186
+ return {
187
+ .type = SQLiteError,
188
+ .message = "[op-sqlite]: Database file not found" + dbPath
189
+ };
224
190
  }
225
- else if (std::holds_alternative<double>(value))
191
+
192
+ remove(dbPath.c_str());
193
+
194
+ return {
195
+ .type = SQLiteOk,
196
+ };
197
+ }
198
+
199
+ inline void bindStatement(sqlite3_stmt *statement, const std::vector<JSVariant> *values)
200
+ {
201
+ size_t size = values->size();
202
+
203
+ for (int ii = 0; ii < size; ii++)
226
204
  {
227
- sqlite3_bind_double(statement, sqIndex, std::get<double>(value));
205
+ int sqIndex = ii + 1;
206
+ JSVariant value = values->at(ii);
207
+
208
+ if (std::holds_alternative<bool>(value))
209
+ {
210
+ sqlite3_bind_int(statement, sqIndex, std::get<bool>(value));
211
+ }
212
+ else if (std::holds_alternative<int>(value))
213
+ {
214
+ sqlite3_bind_int(statement, sqIndex, std::get<int>(value));
215
+ }
216
+ else if (std::holds_alternative<long long>(value))
217
+ {
218
+ sqlite3_bind_double(statement, sqIndex, std::get<long long>(value));
219
+ }
220
+ else if (std::holds_alternative<double>(value))
221
+ {
222
+ sqlite3_bind_double(statement, sqIndex, std::get<double>(value));
223
+ }
224
+ else if (std::holds_alternative<std::string>(value))
225
+ {
226
+ std::string str = std::get<std::string>(value);
227
+ sqlite3_bind_text(statement, sqIndex, str.c_str(), str.length(), SQLITE_TRANSIENT);
228
+ }
229
+ else if(std::holds_alternative<ArrayBuffer>(value))
230
+ {
231
+ ArrayBuffer buffer = std::get<ArrayBuffer>(value);
232
+ sqlite3_bind_blob(statement, sqIndex, buffer.data.get(), buffer.size, SQLITE_STATIC);
233
+ } else {
234
+ sqlite3_bind_null(statement, sqIndex);
235
+ }
228
236
  }
229
- else if (std::holds_alternative<std::string>(value))
237
+ }
238
+
239
+ BridgeResult sqliteExecute(std::string const dbName,
240
+ std::string const &query,
241
+ const std::vector<JSVariant> *params,
242
+ std::vector<DumbHostObject> *results,
243
+ std::shared_ptr<std::vector<DynamicHostObject>> metadatas)
244
+ {
245
+
246
+ if (dbMap.find(dbName) == dbMap.end())
230
247
  {
231
- std::string str = std::get<std::string>(value);
232
- sqlite3_bind_text(statement, sqIndex, str.c_str(), str.length(), SQLITE_TRANSIENT);
248
+ return {
249
+ .type = SQLiteError,
250
+ .message = "[op-sqlite]: Database " + dbName + " is not open"
251
+ };
233
252
  }
234
- else if(std::holds_alternative<ArrayBuffer>(value))
253
+
254
+ sqlite3 *db = dbMap[dbName];
255
+
256
+ sqlite3_stmt *statement;
257
+ const char *errorMessage;
258
+ const char *remainingStatement = nullptr;
259
+
260
+ bool isConsuming = true;
261
+ bool isFailed = false;
262
+
263
+ int result = SQLITE_OK;
264
+
265
+ do {
266
+ const char *queryStr = remainingStatement == nullptr ? query.c_str() : remainingStatement;
267
+
268
+ int statementStatus = sqlite3_prepare_v2(db, queryStr, -1, &statement, &remainingStatement);
269
+
270
+ if (statementStatus == SQLITE_ERROR)
271
+ {
272
+ const char *message = sqlite3_errmsg(db);
273
+ return {
274
+ .type = SQLiteError,
275
+ .message = "[op-sqlite] SQL statement error: " + std::string(message),
276
+ };
277
+ }
278
+
279
+ bindStatement(statement, params);
280
+
281
+ isConsuming = true;
282
+
283
+ int i, count, column_type;
284
+ std::string column_name, column_declared_type;
285
+
286
+ while (isConsuming)
287
+ {
288
+ result = sqlite3_step(statement);
289
+
290
+ switch (result)
291
+ {
292
+ case SQLITE_ROW: {
293
+ if(results == NULL)
294
+ {
295
+ break;
296
+ }
297
+
298
+ i = 0;
299
+ DumbHostObject row = DumbHostObject(metadatas);
300
+
301
+ count = sqlite3_column_count(statement);
302
+
303
+ while (i < count)
304
+ {
305
+ column_type = sqlite3_column_type(statement, i);
306
+
307
+ switch (column_type)
308
+ {
309
+ case SQLITE_INTEGER:
310
+ {
311
+ /**
312
+ * Warning this will loose precision because JS can
313
+ * only represent Integers up to 53 bits
314
+ */
315
+ double column_value = sqlite3_column_double(statement, i);
316
+ row.values.push_back(JSVariant(column_value));
317
+ break;
318
+ }
319
+
320
+ case SQLITE_FLOAT:
321
+ {
322
+ double column_value = sqlite3_column_double(statement, i);
323
+ row.values.push_back(JSVariant(column_value));
324
+ break;
325
+ }
326
+
327
+ case SQLITE_TEXT:
328
+ {
329
+ const char *column_value = reinterpret_cast<const char *>(sqlite3_column_text(statement, i));
330
+ int byteLen = sqlite3_column_bytes(statement, i);
331
+ // Specify length too; in case string contains NULL in the middle
332
+ row.values.push_back(JSVariant(std::string(column_value, byteLen)));
333
+ break;
334
+ }
335
+
336
+ case SQLITE_BLOB:
337
+ {
338
+ int blob_size = sqlite3_column_bytes(statement, i);
339
+ const void *blob = sqlite3_column_blob(statement, i);
340
+ uint8_t *data = new uint8_t[blob_size];
341
+ // You cannot share raw memory between native and JS
342
+ // always copy the data
343
+ memcpy(data, blob, blob_size);
344
+ row.values.push_back(JSVariant(ArrayBuffer {
345
+ .data = std::shared_ptr<uint8_t>{data},
346
+ .size = static_cast<size_t>(blob_size)
347
+ }));
348
+ break;
349
+ }
350
+
351
+ case SQLITE_NULL:
352
+ // Intentionally left blank
353
+
354
+ default:
355
+ row.values.push_back(JSVariant(NULL));
356
+ break;
357
+ }
358
+ i++;
359
+ }
360
+ results->push_back(row);
361
+ break;
362
+ }
363
+
364
+ case SQLITE_DONE:
365
+ i = 0;
366
+ count = sqlite3_column_count(statement);
367
+
368
+ while (i < count)
369
+ {
370
+ column_name = sqlite3_column_name(statement, i);
371
+ const char *type = sqlite3_column_decltype(statement, i);
372
+ auto metadata = DynamicHostObject();
373
+ metadata.fields.push_back(std::make_pair("name", column_name));
374
+ metadata.fields.push_back(std::make_pair("index", i));
375
+ metadata.fields.push_back(std::make_pair("type", type));
376
+
377
+ metadatas->push_back(metadata);
378
+ i++;
379
+ }
380
+
381
+ isConsuming = false;
382
+ break;
383
+
384
+ default:
385
+ errorMessage = sqlite3_errmsg(db);
386
+ isFailed = true;
387
+ isConsuming = false;
388
+ }
389
+ }
390
+
391
+ sqlite3_finalize(statement);
392
+ } while (remainingStatement != NULL && strcmp(remainingStatement, "") != 0 && !isFailed);
393
+
394
+
395
+ if (isFailed)
235
396
  {
236
- ArrayBuffer buffer = std::get<ArrayBuffer>(value);
237
- sqlite3_bind_blob(statement, sqIndex, buffer.data.get(), buffer.size, SQLITE_STATIC);
238
- } else {
239
- sqlite3_bind_null(statement, sqIndex);
397
+
398
+ return {
399
+ .type = SQLiteError,
400
+ .message = "[op-sqlite] SQLite code: " + std::to_string(result) + " execution error: " + std::string(errorMessage)
401
+ };
240
402
  }
241
- }
242
- }
243
-
244
- BridgeResult sqliteExecute(std::string const dbName,
245
- std::string const &query,
246
- std::vector<JSVariant> *params,
247
- std::vector<DumbHostObject> *results,
248
- std::shared_ptr<std::vector<DynamicHostObject>> metadatas)
249
- {
250
-
251
- if (dbMap.find(dbName) == dbMap.end())
252
- {
403
+
404
+ int changedRowCount = sqlite3_changes(db);
405
+ long long latestInsertRowId = sqlite3_last_insert_rowid(db);
406
+
253
407
  return {
254
- .type = SQLiteError,
255
- .message = "[op-sqlite]: Database " + dbName + " is not open"
408
+ .type = SQLiteOk,
409
+ .affectedRows = changedRowCount,
410
+ .insertId = static_cast<double>(latestInsertRowId)
256
411
  };
257
412
  }
258
-
259
- sqlite3 *db = dbMap[dbName];
260
-
261
- sqlite3_stmt *statement;
262
- const char *remainingStatement = nullptr;
263
-
264
- bool isConsuming = true;
265
- bool isFailed = false;
266
-
267
- int result = SQLITE_OK;
268
-
269
- do {
270
- const char *queryStr = remainingStatement == nullptr ? query.c_str() : remainingStatement;
413
+
414
+ BridgeResult sqliteExecuteLiteral(std::string const dbName, std::string const &query)
415
+ {
416
+ if (dbMap.count(dbName) == 0)
417
+ {
418
+ return {
419
+ SQLiteError,
420
+ "[op-sqlite] Database not opened: " + dbName
421
+ };
422
+ }
271
423
 
272
- int statementStatus = sqlite3_prepare_v2(db, queryStr, -1, &statement, &remainingStatement);
424
+ sqlite3 *db = dbMap[dbName];
425
+ sqlite3_stmt *statement;
273
426
 
274
- if (statementStatus == SQLITE_ERROR)
427
+ int statementStatus = sqlite3_prepare_v2(db, query.c_str(), -1, &statement, NULL);
428
+
429
+ if (statementStatus != SQLITE_OK)
275
430
  {
276
431
  const char *message = sqlite3_errmsg(db);
277
432
  return {
278
- .type = SQLiteError,
279
- .message = "[op-sqlite] SQL statement error: " + std::string(message),
280
- };
433
+ SQLiteError,
434
+ "[op-sqlite] SQL statement error: " + std::string(message),
435
+ 0};
281
436
  }
282
437
 
283
- bindStatement(statement, params);
284
-
285
- isConsuming = true;
438
+ bool isConsuming = true;
439
+ bool isFailed = false;
286
440
 
287
- int i, count, column_type;
288
- std::string column_name, column_declared_type;
441
+ int result;
442
+ std::string column_name;
289
443
 
290
444
  while (isConsuming)
291
445
  {
@@ -293,95 +447,11 @@ BridgeResult sqliteExecute(std::string const dbName,
293
447
 
294
448
  switch (result)
295
449
  {
296
- case SQLITE_ROW: {
297
- if(results == NULL)
298
- {
299
- break;
300
- }
301
-
302
- i = 0;
303
- DumbHostObject row = DumbHostObject(metadatas);
304
-
305
- count = sqlite3_column_count(statement);
306
-
307
- while (i < count)
308
- {
309
- column_type = sqlite3_column_type(statement, i);
310
-
311
- switch (column_type)
312
- {
313
- case SQLITE_INTEGER:
314
- {
315
- /**
316
- * Warning this will loose precision because JS can
317
- * only represent Integers up to 53 bits
318
- */
319
- double column_value = sqlite3_column_double(statement, i);
320
- row.values.push_back(JSVariant(column_value));
321
- break;
322
- }
323
-
324
- case SQLITE_FLOAT:
325
- {
326
- double column_value = sqlite3_column_double(statement, i);
327
- row.values.push_back(JSVariant(column_value));
328
- break;
329
- }
330
-
331
- case SQLITE_TEXT:
332
- {
333
- const char *column_value = reinterpret_cast<const char *>(sqlite3_column_text(statement, i));
334
- int byteLen = sqlite3_column_bytes(statement, i);
335
- // Specify length too; in case string contains NULL in the middle
336
- row.values.push_back(JSVariant(std::string(column_value, byteLen)));
337
- break;
338
- }
339
-
340
- case SQLITE_BLOB:
341
- {
342
- int blob_size = sqlite3_column_bytes(statement, i);
343
- const void *blob = sqlite3_column_blob(statement, i);
344
- uint8_t *data = new uint8_t[blob_size];
345
- // You cannot share raw memory between native and JS
346
- // always copy the data
347
- memcpy(data, blob, blob_size);
348
- row.values.push_back(JSVariant(ArrayBuffer {
349
- .data = std::shared_ptr<uint8_t>{data},
350
- .size = static_cast<size_t>(blob_size)
351
- }));
352
- break;
353
- }
354
-
355
- case SQLITE_NULL:
356
- // Intentionally left blank
357
-
358
- default:
359
- row.values.push_back(JSVariant(NULL));
360
- break;
361
- }
362
- i++;
363
- }
364
- results->push_back(row);
450
+ case SQLITE_ROW:
451
+ isConsuming = true;
365
452
  break;
366
- }
367
453
 
368
454
  case SQLITE_DONE:
369
- i = 0;
370
- count = sqlite3_column_count(statement);
371
-
372
- while (i < count)
373
- {
374
- column_name = sqlite3_column_name(statement, i);
375
- const char *type = sqlite3_column_decltype(statement, i);
376
- auto metadata = DynamicHostObject();
377
- metadata.fields.push_back(std::make_pair("name", column_name));
378
- metadata.fields.push_back(std::make_pair("index", i));
379
- metadata.fields.push_back(std::make_pair("type", type));
380
-
381
- metadatas->push_back(metadata);
382
- i++;
383
- }
384
-
385
455
  isConsuming = false;
386
456
  break;
387
457
 
@@ -392,96 +462,86 @@ BridgeResult sqliteExecute(std::string const dbName,
392
462
  }
393
463
 
394
464
  sqlite3_finalize(statement);
395
- } while (remainingStatement != NULL && strcmp(remainingStatement, "") != 0 && !isFailed);
396
-
397
-
398
- if (isFailed)
399
- {
400
- const char *message = sqlite3_errmsg(db);
465
+
466
+ if (isFailed)
467
+ {
468
+ const char *message = sqlite3_errmsg(db);
469
+ return {
470
+ SQLiteError,
471
+ "[op-sqlite] SQL execution error: " + std::string(message),
472
+ 0};
473
+ }
474
+
475
+ int changedRowCount = sqlite3_changes(db);
401
476
  return {
402
- .type = SQLiteError,
403
- .message = "[op-sqlite] SQLite code: " + std::to_string(result) + " execution error: " + std::string(message)
404
- };
477
+ SQLiteOk,
478
+ "",
479
+ changedRowCount};
405
480
  }
406
-
407
- int changedRowCount = sqlite3_changes(db);
408
- long long latestInsertRowId = sqlite3_last_insert_rowid(db);
409
-
410
- return {
411
- .type = SQLiteOk,
412
- .affectedRows = changedRowCount,
413
- .insertId = static_cast<double>(latestInsertRowId)
414
- };
415
- }
416
481
 
417
- BridgeResult sqliteExecuteLiteral(std::string const dbName, std::string const &query)
418
- {
419
- // Check if db connection is opened
420
- if (dbMap.count(dbName) == 0)
421
- {
422
- return {
423
- SQLiteError,
424
- "[op-sqlite] Database not opened: " + dbName
425
- };
482
+ void sqliteCloseAll() {
483
+ for (auto const& x : dbMap) {
484
+ // Interrupt will make all pending operations to fail with SQLITE_INTERRUPT
485
+ // The ongoing work from threads will then fail ASAP
486
+ sqlite3_interrupt(x.second);
487
+ // Each DB connection can then be safely interrupted
488
+ sqlite3_close_v2(x.second);
489
+ }
490
+ dbMap.clear();
426
491
  }
427
-
428
- sqlite3 *db = dbMap[dbName];
429
- sqlite3_stmt *statement;
430
-
431
- // Compile and move result into statement memory spot
432
- int statementStatus = sqlite3_prepare_v2(db, query.c_str(), -1, &statement, NULL);
433
-
434
- if (statementStatus != SQLITE_OK)
492
+
493
+ std::string operationToString(int operation_type) {
494
+ switch (operation_type) {
495
+ case SQLITE_INSERT:
496
+ return "INSERT";
497
+
498
+ case SQLITE_DELETE:
499
+ return "DELETE";
500
+
501
+ case SQLITE_UPDATE:
502
+ return "UPDATE";
503
+
504
+ default:
505
+ throw std::invalid_argument("Uknown SQLite operation on hook");
506
+ }
507
+ }
508
+
509
+ void update_callback ( void *dbName, int operation_type,
510
+ char const *database, char const *table,
511
+ sqlite3_int64 rowid)
435
512
  {
436
- const char *message = sqlite3_errmsg(db);
437
- return {
438
- SQLiteError,
439
- "[op-sqlite] SQL statement error: " + std::string(message),
440
- 0};
513
+ std::string &strDbName = *(static_cast<std::string*>(dbName));
514
+ auto callback = callbackMap[strDbName];
515
+ callback(strDbName, std::string(table), operationToString(operation_type), static_cast<int>(rowid));
441
516
  }
442
517
 
443
- bool isConsuming = true;
444
- bool isFailed = false;
445
-
446
- int result;
447
- std::string column_name;
448
-
449
- while (isConsuming)
450
- {
451
- result = sqlite3_step(statement);
452
-
453
- switch (result)
518
+ BridgeResult registerUpdateHook(std::string const dbName, std::function<void (std::string dbName, std::string tableName, std::string operation, int rowId)> const callback) {
519
+ if (dbMap.count(dbName) == 0)
454
520
  {
455
- case SQLITE_ROW:
456
- isConsuming = true;
457
- break;
458
-
459
- case SQLITE_DONE:
460
- isConsuming = false;
461
- break;
462
-
463
- default:
464
- isFailed = true;
465
- isConsuming = false;
521
+ return {
522
+ SQLiteError,
523
+ "[op-sqlite] Database not opened: " + dbName
524
+ };
466
525
  }
467
- }
468
-
469
- sqlite3_finalize(statement);
470
-
471
- if (isFailed)
472
- {
473
- const char *message = sqlite3_errmsg(db);
526
+
527
+ sqlite3 *db = dbMap[dbName];
528
+ callbackMap[dbName] = callback;
529
+ const std::string *key = nullptr;
530
+
531
+ // TODO find a more elegant way to retrieve a reference to the key
532
+ for (auto const& element : dbMap) {
533
+ if(element.first == dbName) {
534
+ key = &element.first;
535
+ }
536
+ }
537
+
538
+ sqlite3_update_hook(
539
+ db,
540
+ &update_callback,
541
+ (void *)key);
542
+
474
543
  return {
475
- SQLiteError,
476
- "[op-sqlite] SQL execution error: " + std::string(message),
477
- 0};
544
+ SQLiteOk
545
+ };
478
546
  }
479
-
480
- int changedRowCount = sqlite3_changes(db);
481
- return {
482
- SQLiteOk,
483
- "",
484
- changedRowCount};
485
- }
486
-
487
547
  }