@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/README.md +27 -20
- package/cpp/ThreadPool.cpp +116 -81
- package/cpp/ThreadPool.h +26 -25
- package/cpp/bindings.cpp +66 -9
- package/cpp/bridge.cpp +446 -386
- package/cpp/bridge.h +3 -1
- package/cpp/macros.h +0 -5
- package/lib/commonjs/index.js +2 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +2 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/index.d.ts +13 -0
- package/package.json +1 -1
- package/src/index.ts +20 -0
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
|
-
|
|
54
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
if (dbMap.count(dbName) == 0)
|
|
70
|
+
inline bool file_exists(const std::string &path)
|
|
108
71
|
{
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
181
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
161
|
+
return BridgeResult{
|
|
162
|
+
.type = SQLiteError,
|
|
163
|
+
.message = mainDBName + "was unable to detach database: " + std::string(result.message),
|
|
164
|
+
};
|
|
216
165
|
}
|
|
217
|
-
|
|
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
|
-
|
|
175
|
+
BridgeResult closeResult = sqliteCloseDb(dbName);
|
|
176
|
+
if (closeResult.type == SQLiteError)
|
|
177
|
+
{
|
|
178
|
+
return closeResult;
|
|
179
|
+
}
|
|
220
180
|
}
|
|
221
|
-
|
|
181
|
+
|
|
182
|
+
std::string dbPath = get_db_path(dbName, docPath);
|
|
183
|
+
|
|
184
|
+
if (!file_exists(dbPath))
|
|
222
185
|
{
|
|
223
|
-
|
|
186
|
+
return {
|
|
187
|
+
.type = SQLiteError,
|
|
188
|
+
.message = "[op-sqlite]: Database file not found" + dbPath
|
|
189
|
+
};
|
|
224
190
|
}
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
232
|
-
|
|
248
|
+
return {
|
|
249
|
+
.type = SQLiteError,
|
|
250
|
+
.message = "[op-sqlite]: Database " + dbName + " is not open"
|
|
251
|
+
};
|
|
233
252
|
}
|
|
234
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
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 =
|
|
255
|
-
.
|
|
408
|
+
.type = SQLiteOk,
|
|
409
|
+
.affectedRows = changedRowCount,
|
|
410
|
+
.insertId = static_cast<double>(latestInsertRowId)
|
|
256
411
|
};
|
|
257
412
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
424
|
+
sqlite3 *db = dbMap[dbName];
|
|
425
|
+
sqlite3_stmt *statement;
|
|
273
426
|
|
|
274
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
433
|
+
SQLiteError,
|
|
434
|
+
"[op-sqlite] SQL statement error: " + std::string(message),
|
|
435
|
+
0};
|
|
281
436
|
}
|
|
282
437
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
isConsuming = true;
|
|
438
|
+
bool isConsuming = true;
|
|
439
|
+
bool isFailed = false;
|
|
286
440
|
|
|
287
|
-
int
|
|
288
|
-
std::string column_name
|
|
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
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
|
|
403
|
-
|
|
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
|
-
|
|
418
|
-
{
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
-
|
|
444
|
-
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const
|
|
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
|
-
|
|
476
|
-
|
|
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
|
}
|