Skip to content

Commit a069019

Browse files
author
Oscar Franco
authored
Merge pull request #32 from EduFrazao/async-callback-threads
2 parents b799900 + ea6de4f commit a069019

8 files changed

Lines changed: 296 additions & 115 deletions

File tree

android/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ add_library(
4949
../cpp/JSIHelper.cpp
5050
../cpp/ThreadPool.h
5151
../cpp/ThreadPool.cpp
52+
../cpp/sqlfileloader.h
53+
../cpp/sqlfileloader.cpp
54+
../cpp/sqlbatchexecutor.h
55+
../cpp/sqlbatchexecutor.cpp
5256
cpp-adapter.cpp
5357
)
5458

cpp/installer.cpp

Lines changed: 95 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
#include "logs.h"
1313
#include "JSIHelper.h"
1414
#include "ThreadPool.h"
15+
#include "sqlfileloader.h"
16+
#include "sqlbatchexecutor.h"
1517
#include <vector>
16-
#include <iostream>
17-
#include <fstream>
1818
#include <string>
1919

2020
using namespace std;
@@ -23,55 +23,6 @@ using namespace facebook;
2323
string docPathStr;
2424
std::shared_ptr<react::CallInvoker> invoker;
2525

26-
/**
27-
* Local function to handle SQL File Import in order to reuse with Sync and Async operations
28-
*/
29-
SequelBatchOperationResult importSQLFile(string dbName, string fileLocation)
30-
{
31-
string line;
32-
ifstream sqFile(fileLocation);
33-
if (sqFile.is_open())
34-
{
35-
try
36-
{
37-
int affectedRows = 0;
38-
int commands = 0;
39-
sequel_execute_literal_update(dbName, "BEGIN EXCLUSIVE TRANSACTION");
40-
while (std::getline(sqFile, line, '\n'))
41-
{
42-
if (!line.empty())
43-
{
44-
SequelLiteralUpdateResult result = sequel_execute_literal_update(dbName, line);
45-
if (result.type == SequelResultError)
46-
{
47-
sequel_execute_literal_update(dbName, "ROLLBACK");
48-
sqFile.close();
49-
return {SequelResultError, result.message, 0, commands};
50-
}
51-
else
52-
{
53-
affectedRows += result.affectedRows;
54-
commands++;
55-
}
56-
}
57-
}
58-
sqFile.close();
59-
sequel_execute_literal_update(dbName, "COMMIT");
60-
return {SequelResultOk, "", affectedRows, commands};
61-
}
62-
catch (...)
63-
{
64-
sqFile.close();
65-
sequel_execute_literal_update(dbName, "ROLLBACK");
66-
return {SequelResultError, "[react-native-quick-sqlite][loadSQLFile] Unexpected error, transaction was rolledback", 0, 0};
67-
}
68-
}
69-
else
70-
{
71-
return {SequelResultError, "[react-native-quick-sqlite][loadSQLFile] Could not open file", 0, 0};
72-
}
73-
}
74-
7526
jsi::Object createError(jsi::Runtime &rt, string message)
7627
{
7728
auto res = jsi::Object(rt);
@@ -235,7 +186,7 @@ void install(jsi::Runtime &rt, std::shared_ptr<react::CallInvoker> jsCallInvoker
235186

236187
// Converting results into a JSI Response
237188
auto jsiResult = createSequelQueryExecutionResult(rt, status, &results);
238-
return jsiResult;
189+
return move(jsiResult);
239190
});
240191

241192
// Execute a batch of SQL queries in a transaction
@@ -251,80 +202,90 @@ void install(jsi::Runtime &rt, std::shared_ptr<react::CallInvoker> jsCallInvoker
251202
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - Incorrect parameter count");
252203
}
253204

254-
const string dbName = args[0].asString(rt).utf8(rt);
255205
const jsi::Value &params = args[1];
256206
if (params.isNull() || params.isUndefined())
257207
{
258208
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - An array of SQL commands or parameters is needed");
259209
}
210+
const string dbName = args[0].asString(rt).utf8(rt);
211+
const jsi::Array &batchParams = params.asObject(rt).asArray(rt);
212+
vector<QuickQueryArguments> commands;
213+
jsiBatchParametersToQuickArguments(rt, batchParams, &commands);
214+
215+
auto batchResult = executeBatch(dbName, &commands);
216+
if(batchResult.type == SequelResultOk)
217+
{
218+
auto res = jsi::Object(rt);
219+
res.setProperty(rt, "status", jsi::Value(0));
220+
res.setProperty(rt, "rowsAffected", jsi::Value(batchResult.affectedRows));
221+
return move(res);
222+
} else
223+
{
224+
return createError(rt, batchResult.message);
225+
}
226+
});
227+
228+
auto execSQLBatchAsync = jsi::Function::createFromHostFunction(
229+
rt,
230+
jsi::PropNameID::forAscii(rt, "sequel_execSQLBatchAsync"),
231+
3,
232+
[pool](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
233+
{
234+
if (sizeof(args) < 3)
235+
{
236+
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncExecuteSqlBatch] Incorrect parameter count");
237+
return {};
238+
}
239+
240+
const jsi::Value &params = args[1];
241+
const jsi::Value &callbackHolder = args[2];
242+
if(!callbackHolder.isObject() || !callbackHolder.asObject(rt).isFunction(rt)) {
243+
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncExecuteSqlBatch] The callback argument must be a function");
244+
return {};
245+
}
246+
247+
if (params.isNull() || params.isUndefined())
248+
{
249+
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncExecuteSqlBatch] - An array of SQL commands or parameters is needed");
250+
return {};
251+
}
260252

261-
int rowsAffected = 0;
253+
const string dbName = args[0].asString(rt).utf8(rt);
262254
const jsi::Array &batchParams = params.asObject(rt).asArray(rt);
263-
try
255+
auto callback = make_shared<jsi::Value>(callbackHolder.asObject(rt));
256+
257+
vector<QuickQueryArguments> commands;
258+
jsiBatchParametersToQuickArguments(rt, batchParams, &commands);
259+
260+
auto task =
261+
[&rt, dbName, commands = make_shared<vector<QuickQueryArguments>>(commands), callback]()
264262
{
265-
sequel_execute(rt, dbName, "BEGIN TRANSACTION", jsi::Value::undefined());
266-
for (int i = 0; i < batchParams.length(rt); i++)
263+
try
267264
{
268-
const jsi::Array &command = batchParams.getValueAtIndex(rt, i).asObject(rt).asArray(rt);
269-
if (command.length(rt) == 0)
270-
{
271-
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());
272-
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - No SQL Commands found on batch index " + std::to_string(i));
273-
}
274-
const string query = command.getValueAtIndex(rt, 0).asString(rt).utf8(rt);
275-
const jsi::Value &commandParams = command.length(rt) > 1 ? command.getValueAtIndex(rt, 1) : jsi::Value::undefined();
276-
if (!commandParams.isUndefined() && commandParams.asObject(rt).isArray(rt) && commandParams.asObject(rt).asArray(rt).length(rt) > 0 && commandParams.asObject(rt).asArray(rt).getValueAtIndex(rt, 0).isObject())
277-
{
278-
// This arguments are an array of arrays, like a batch update of a single sql command.
279-
const jsi::Array &batchUpdateParams = commandParams.asObject(rt).asArray(rt);
280-
for (int x = 0; x < batchUpdateParams.length(rt); x++)
281-
{
282-
const jsi::Value &p = batchUpdateParams.getValueAtIndex(rt, x);
283-
SequelResult result = sequel_execute(rt, dbName, query, p);
284-
if (result.type == SequelResultError)
285-
{
286-
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());
287-
return createError(rt, result.message.c_str());
288-
}
289-
else
290-
{
291-
if (result.value.getObject(rt).hasProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")))
292-
{
293-
rowsAffected += result.value.getObject(rt).getProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")).asNumber();
294-
}
295-
}
296-
}
297-
}
298-
else
265+
// Inside the new worker thread, we can now call sqlite operations
266+
auto batchResult = executeBatch(dbName, commands.get());
267+
invoker->invokeAsync([&rt, batchResult = move(batchResult), callback]
299268
{
300-
SequelResult result = sequel_execute(rt, dbName, query, commandParams);
301-
if (result.type == SequelResultError)
269+
if(batchResult.type == SequelResultOk)
302270
{
303-
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());
304-
305-
return createError(rt, result.message.c_str());
306-
}
307-
else
271+
auto res = jsi::Object(rt);
272+
res.setProperty(rt, "status", jsi::Value(0));
273+
res.setProperty(rt, "rowsAffected", jsi::Value(batchResult.affectedRows));
274+
callback->asObject(rt).asFunction(rt).call(rt, move(res));
275+
} else
308276
{
309-
if (result.value.getObject(rt).hasProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")))
310-
{
311-
rowsAffected += result.value.getObject(rt).getProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")).asNumber();
312-
}
277+
callback->asObject(rt).asFunction(rt).call(rt, createError(rt, batchResult.message));
313278
}
314-
}
279+
});
315280
}
316-
sequel_execute(rt, dbName, "COMMIT", jsi::Value::undefined());
317-
}
318-
catch (...)
319-
{
320-
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());
321-
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - Unexpected error");
322-
}
323-
324-
auto res = jsi::Object(rt);
325-
res.setProperty(rt, "status", jsi::Value(0));
326-
res.setProperty(rt, "rowsAffected", jsi::Value(rowsAffected));
327-
return move(res);
281+
catch (std::exception &exc)
282+
{
283+
invoker->invokeAsync([&rt, callback, &exc]
284+
{ callback->asObject(rt).asFunction(rt).call(rt, createError(rt, exc.what())); });
285+
}
286+
};
287+
pool->queueWork(task);
288+
return {};
328289
});
329290

330291
// Load SQL File from disk
@@ -359,9 +320,21 @@ void install(jsi::Runtime &rt, std::shared_ptr<react::CallInvoker> jsCallInvoker
359320
3,
360321
[pool](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
361322
{
323+
if (sizeof(args) < 3)
324+
{
325+
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncLoadSqlFile] Incorrect parameter count");
326+
return {};
327+
}
328+
329+
const jsi::Value &callbackHolder = args[2];
330+
if(!callbackHolder.isObject() || !callbackHolder.asObject(rt).isFunction(rt)) {
331+
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncLoadSqlFile] The callback argument must be a function");
332+
return {};
333+
}
334+
362335
const string dbName = args[0].asString(rt).utf8(rt);
363336
const string sqlFileName = args[1].asString(rt).utf8(rt);
364-
auto callback = make_shared<jsi::Value>((args[2].asObject(rt)));
337+
auto callback = make_shared<jsi::Value>(callbackHolder.asObject(rt));
365338

366339
auto task =
367340
[&rt, dbName, sqlFileName, callback]()
@@ -405,14 +378,20 @@ void install(jsi::Runtime &rt, std::shared_ptr<react::CallInvoker> jsCallInvoker
405378
{
406379
if (count < 4)
407380
{
408-
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite] Incorrect arguments for asyncExecuteSQL");
381+
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncExecuteSql] Incorrect arguments for asyncExecuteSQL");
382+
return {};
383+
}
384+
385+
const jsi::Value &callbackHolder = args[3];
386+
if(!callbackHolder.isObject() || !callbackHolder.asObject(rt).isFunction(rt)) {
387+
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncExecuteSql] The callback argument must be a function");
409388
return {};
410389
}
411390

412391
const string dbName = args[0].asString(rt).utf8(rt);
413392
const string query = args[1].asString(rt).utf8(rt);
414393
const jsi::Value &originalParams = args[2];
415-
auto callback = make_shared<jsi::Value>(args[3].asObject(rt));
394+
auto callback = make_shared<jsi::Value>(callbackHolder.asObject(rt));
416395

417396
// Converting query parameters inside the javascript caller thread
418397
vector<QuickValue> params;
@@ -456,6 +435,7 @@ void install(jsi::Runtime &rt, std::shared_ptr<react::CallInvoker> jsCallInvoker
456435
module.setProperty(rt, "executeSql", move(execSQL));
457436
module.setProperty(rt, "asyncExecuteSql", move(asyncExecSQL));
458437
module.setProperty(rt, "executeSqlBatch", move(execSQLBatch));
438+
module.setProperty(rt, "asyncExecuteSqlBatch", move(execSQLBatchAsync));
459439
module.setProperty(rt, "loadSqlFile", move(loadSQLFile));
460440
module.setProperty(rt, "asyncLoadSqlFile", move(loadSQLFileAsync));
461441

cpp/sqlbatchexecutor.cpp

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Batch execution implementation
3+
*/
4+
#include "sqlbatchexecutor.h"
5+
6+
void jsiBatchParametersToQuickArguments(jsi::Runtime &rt, jsi::Array const &batchParams, vector<QuickQueryArguments> *commands)
7+
{
8+
for (int i = 0; i < batchParams.length(rt); i++)
9+
{
10+
const jsi::Array &command = batchParams.getValueAtIndex(rt, i).asObject(rt).asArray(rt);
11+
if (command.length(rt) == 0)
12+
{
13+
continue;
14+
}
15+
16+
const string query = command.getValueAtIndex(rt, 0).asString(rt).utf8(rt);
17+
const jsi::Value &commandParams = command.length(rt) > 1 ? command.getValueAtIndex(rt, 1) : jsi::Value::undefined();
18+
if (!commandParams.isUndefined() && commandParams.asObject(rt).isArray(rt) && commandParams.asObject(rt).asArray(rt).length(rt) > 0 && commandParams.asObject(rt).asArray(rt).getValueAtIndex(rt, 0).isObject())
19+
{
20+
// This arguments is an array of arrays, like a batch update of a single sql command.
21+
const jsi::Array &batchUpdateParams = commandParams.asObject(rt).asArray(rt);
22+
for (int x = 0; x < batchUpdateParams.length(rt); x++)
23+
{
24+
const jsi::Value &p = batchUpdateParams.getValueAtIndex(rt, x);
25+
vector<QuickValue> params;
26+
jsiQueryArgumentsToSequelParam(rt, p, &params);
27+
commands->push_back(QuickQueryArguments{
28+
query,
29+
make_shared<vector<QuickValue>>(params)
30+
});
31+
}
32+
}
33+
else
34+
{
35+
vector<QuickValue> params;
36+
jsiQueryArgumentsToSequelParam(rt, commandParams, &params);
37+
commands->push_back(QuickQueryArguments{
38+
query,
39+
make_shared<vector<QuickValue>>(params)
40+
});
41+
}
42+
}
43+
}
44+
45+
SequelBatchOperationResult executeBatch(std::string dbName, vector<QuickQueryArguments> *commands)
46+
{
47+
size_t commandCount = commands->size();
48+
if(commandCount <= 0)
49+
{
50+
return SequelBatchOperationResult {
51+
.type = SequelResultError,
52+
.message = "No SQL commands provided",
53+
};
54+
}
55+
56+
try
57+
{
58+
int affectedRows = 0;
59+
sequel_execute_literal_update(dbName, "BEGIN EXCLUSIVE TRANSACTION");
60+
for(int i = 0; i<commandCount; i++) {
61+
auto command = commands->at(i);
62+
// We do not provide a datastructure to receive query data because we don't need/want to handle this results in a batch execution
63+
auto result = sequel_execute3(dbName, command.sql, command.params.get(), NULL);
64+
if(result.type == SequelResultError)
65+
{
66+
return SequelBatchOperationResult {
67+
.type = SequelResultError,
68+
.message = result.errorMessage,
69+
};
70+
} else
71+
{
72+
affectedRows += result.rowsAffected;
73+
}
74+
}
75+
sequel_execute_literal_update(dbName, "COMMIT");
76+
return SequelBatchOperationResult {
77+
.type = SequelResultOk,
78+
.affectedRows = affectedRows,
79+
.commands = (int) commandCount,
80+
};
81+
} catch(std::exception &exc)
82+
{
83+
sequel_execute_literal_update(dbName, "ROLLBACK");
84+
return SequelBatchOperationResult {
85+
.type = SequelResultError,
86+
.message = exc.what(),
87+
};
88+
}
89+
}

0 commit comments

Comments
 (0)