Skip to content

Commit 273f695

Browse files
committed
Refactory of batchExecution
- Exported to this own file - Created the async version of this feature
1 parent 2196756 commit 273f695

7 files changed

Lines changed: 204 additions & 60 deletions

File tree

android/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ add_library(
5151
../cpp/ThreadPool.cpp
5252
../cpp/sqlfileloader.h
5353
../cpp/sqlfileloader.cpp
54+
../cpp/sqlbatchexecutor.h
55+
../cpp/sqlbatchexecutor.cpp
5456
cpp-adapter.cpp
5557
)
5658

cpp/installer.cpp

Lines changed: 66 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "JSIHelper.h"
1414
#include "ThreadPool.h"
1515
#include "sqlfileloader.h"
16+
#include "sqlbatchexecutor.h"
1617
#include <vector>
1718
#include <string>
1819

@@ -185,7 +186,7 @@ void install(jsi::Runtime &rt, std::shared_ptr<react::CallInvoker> jsCallInvoker
185186

186187
// Converting results into a JSI Response
187188
auto jsiResult = createSequelQueryExecutionResult(rt, status, &results);
188-
return jsiResult;
189+
return move(jsiResult);
189190
});
190191

191192
// Execute a batch of SQL queries in a transaction
@@ -201,80 +202,84 @@ void install(jsi::Runtime &rt, std::shared_ptr<react::CallInvoker> jsCallInvoker
201202
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - Incorrect parameter count");
202203
}
203204

205+
const jsi::Value &params = args[1];
206+
if (params.isNull() || params.isUndefined())
207+
{
208+
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - An array of SQL commands or parameters is needed");
209+
}
204210
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+
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - Incorrect parameter count");
237+
}
238+
205239
const jsi::Value &params = args[1];
206240
if (params.isNull() || params.isUndefined())
207241
{
208242
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - An array of SQL commands or parameters is needed");
209243
}
210244

211-
int rowsAffected = 0;
245+
246+
const string dbName = args[0].asString(rt).utf8(rt);
212247
const jsi::Array &batchParams = params.asObject(rt).asArray(rt);
213-
try
248+
auto callback = make_shared<jsi::Value>((args[2].asObject(rt)));
249+
250+
vector<QuickQueryArguments> commands;
251+
jsiBatchParametersToQuickArguments(rt, batchParams, &commands);
252+
253+
auto task =
254+
[&rt, dbName, commands = make_shared<vector<QuickQueryArguments>>(commands), callback]()
214255
{
215-
sequel_execute(rt, dbName, "BEGIN TRANSACTION", jsi::Value::undefined());
216-
for (int i = 0; i < batchParams.length(rt); i++)
256+
try
217257
{
218-
const jsi::Array &command = batchParams.getValueAtIndex(rt, i).asObject(rt).asArray(rt);
219-
if (command.length(rt) == 0)
220-
{
221-
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());
222-
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - No SQL Commands found on batch index " + std::to_string(i));
223-
}
224-
const string query = command.getValueAtIndex(rt, 0).asString(rt).utf8(rt);
225-
const jsi::Value &commandParams = command.length(rt) > 1 ? command.getValueAtIndex(rt, 1) : jsi::Value::undefined();
226-
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())
227-
{
228-
// This arguments are an array of arrays, like a batch update of a single sql command.
229-
const jsi::Array &batchUpdateParams = commandParams.asObject(rt).asArray(rt);
230-
for (int x = 0; x < batchUpdateParams.length(rt); x++)
231-
{
232-
const jsi::Value &p = batchUpdateParams.getValueAtIndex(rt, x);
233-
SequelResult result = sequel_execute(rt, dbName, query, p);
234-
if (result.type == SequelResultError)
235-
{
236-
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());
237-
return createError(rt, result.message.c_str());
238-
}
239-
else
240-
{
241-
if (result.value.getObject(rt).hasProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")))
242-
{
243-
rowsAffected += result.value.getObject(rt).getProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")).asNumber();
244-
}
245-
}
246-
}
247-
}
248-
else
258+
// Inside the new worker thread, we can now call sqlite operations
259+
auto batchResult = executeBatch(dbName, commands.get());
260+
invoker->invokeAsync([&rt, batchResult = move(batchResult), callback]
249261
{
250-
SequelResult result = sequel_execute(rt, dbName, query, commandParams);
251-
if (result.type == SequelResultError)
262+
if(batchResult.type == SequelResultOk)
252263
{
253-
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());
254-
255-
return createError(rt, result.message.c_str());
256-
}
257-
else
264+
auto res = jsi::Object(rt);
265+
res.setProperty(rt, "status", jsi::Value(0));
266+
res.setProperty(rt, "rowsAffected", jsi::Value(batchResult.affectedRows));
267+
callback->asObject(rt).asFunction(rt).call(rt, move(res));
268+
return move(res);
269+
} else
258270
{
259-
if (result.value.getObject(rt).hasProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")))
260-
{
261-
rowsAffected += result.value.getObject(rt).getProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")).asNumber();
262-
}
271+
callback->asObject(rt).asFunction(rt).call(rt, createError(rt, batchResult.message));
263272
}
264-
}
273+
});
265274
}
266-
sequel_execute(rt, dbName, "COMMIT", jsi::Value::undefined());
267-
}
268-
catch (...)
269-
{
270-
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());
271-
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - Unexpected error");
272-
}
273-
274-
auto res = jsi::Object(rt);
275-
res.setProperty(rt, "status", jsi::Value(0));
276-
res.setProperty(rt, "rowsAffected", jsi::Value(rowsAffected));
277-
return move(res);
275+
catch (std::exception &exc)
276+
{
277+
invoker->invokeAsync([&rt, callback, &exc]
278+
{ callback->asObject(rt).asFunction(rt).call(rt, createError(rt, exc.what())); });
279+
}
280+
};
281+
pool->queueWork(task);
282+
return {};
278283
});
279284

280285
// Load SQL File from disk
@@ -406,6 +411,7 @@ void install(jsi::Runtime &rt, std::shared_ptr<react::CallInvoker> jsCallInvoker
406411
module.setProperty(rt, "executeSql", move(execSQL));
407412
module.setProperty(rt, "asyncExecuteSql", move(asyncExecSQL));
408413
module.setProperty(rt, "executeSqlBatch", move(execSQLBatch));
414+
module.setProperty(rt, "asyncExecuteSqlBatch", move(execSQLBatchAsync));
409415
module.setProperty(rt, "loadSqlFile", move(loadSQLFile));
410416
module.setProperty(rt, "asyncLoadSqlFile", move(loadSQLFileAsync));
411417

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+
}

cpp/sqlbatchexecutor.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* SQL Batch execution implementation using default sqliteBridge implementation
3+
*/
4+
#include "JSIHelper.h"
5+
#include "sqliteBridge.h"
6+
7+
using namespace std;
8+
using namespace facebook;
9+
10+
struct QuickQueryArguments {
11+
string sql;
12+
shared_ptr<vector<QuickValue>> params;
13+
};
14+
15+
/**
16+
* Local Helper method to translate JSI objects QuickQueryArguments datastructure
17+
* MUST be called in the JavaScript Thread
18+
*/
19+
void jsiBatchParametersToQuickArguments(jsi::Runtime &rt, jsi::Array const &batchParams, vector<QuickQueryArguments> *commands);
20+
21+
/**
22+
* Execute a batch of commands in a exclusive transaction
23+
*/
24+
SequelBatchOperationResult executeBatch(std::string dbName, vector<QuickQueryArguments> *commands);

cpp/sqliteBridge.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,11 @@ SequelOperationStatus sequel_execute3(string const dbName, string const &query,
334334
switch (result)
335335
{
336336
case SQLITE_ROW:
337+
if(results == NULL)
338+
{
339+
break;
340+
}
341+
337342
i = 0;
338343
row = map<string, QuickValue>();
339344
count = sqlite3_column_count(statement);

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,8 @@
138138
}
139139
]
140140
]
141+
},
142+
"dependencies": {
143+
"react-native-quick-sqlite": "file:///home/eduardo/Projetos/react/plugins/react-native-quick-sqlite"
141144
}
142145
}

src/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ interface ISQLite {
9090
dbName: string,
9191
commands: SQLBatchParams[]
9292
) => BatchQueryResult;
93+
asyncExecuteSqlBatch: (
94+
dbName: string,
95+
commands: SQLBatchParams[],
96+
cb: (res: BatchQueryResult) => void
97+
) => void;
9398
loadSqlFile: (dbName: string, location: string) => FileLoadResult;
9499
asyncLoadSqlFile: (
95100
dbName: string,
@@ -124,6 +129,10 @@ interface IDBConnection {
124129
commands: SQLBatchParams[],
125130
callback?: (res: BatchQueryResult) => void
126131
) => void;
132+
asyncExecuteSqlBatch: (
133+
commands: SQLBatchParams[],
134+
cb: (res: BatchQueryResult) => void
135+
) => void;
127136
close: (ok: (res: any) => void, fail: (msg: string) => void) => void;
128137
loadSqlFile: (
129138
location: string,
@@ -189,6 +198,12 @@ export const openDatabase = (
189198
const response = sqlite.executeSqlBatch(options.name, commands);
190199
if (callback) callback(response);
191200
},
201+
asyncExecuteSqlBatch: (
202+
commands: SQLBatchParams[],
203+
cb: (res: BatchQueryResult) => void
204+
) => {
205+
sqlite.asyncExecuteSqlBatch(options.name, commands, cb);
206+
},
192207
close: (ok: any, fail: any) => {
193208
try {
194209
sqlite.close(options.name);

0 commit comments

Comments
 (0)