Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ add_library(
../cpp/JSIHelper.cpp
../cpp/ThreadPool.h
../cpp/ThreadPool.cpp
../cpp/sqlfileloader.h
../cpp/sqlfileloader.cpp
../cpp/sqlbatchexecutor.h
../cpp/sqlbatchexecutor.cpp
cpp-adapter.cpp
)

Expand Down
210 changes: 95 additions & 115 deletions cpp/installer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
#include "logs.h"
#include "JSIHelper.h"
#include "ThreadPool.h"
#include "sqlfileloader.h"
#include "sqlbatchexecutor.h"
#include <vector>
#include <iostream>
#include <fstream>
#include <string>

using namespace std;
Expand All @@ -23,55 +23,6 @@ using namespace facebook;
string docPathStr;
std::shared_ptr<react::CallInvoker> invoker;

/**
* Local function to handle SQL File Import in order to reuse with Sync and Async operations
*/
SequelBatchOperationResult importSQLFile(string dbName, string fileLocation)
{
string line;
ifstream sqFile(fileLocation);
if (sqFile.is_open())
{
try
{
int affectedRows = 0;
int commands = 0;
sequel_execute_literal_update(dbName, "BEGIN EXCLUSIVE TRANSACTION");
while (std::getline(sqFile, line, '\n'))
{
if (!line.empty())
{
SequelLiteralUpdateResult result = sequel_execute_literal_update(dbName, line);
if (result.type == SequelResultError)
{
sequel_execute_literal_update(dbName, "ROLLBACK");
sqFile.close();
return {SequelResultError, result.message, 0, commands};
}
else
{
affectedRows += result.affectedRows;
commands++;
}
}
}
sqFile.close();
sequel_execute_literal_update(dbName, "COMMIT");
return {SequelResultOk, "", affectedRows, commands};
}
catch (...)
{
sqFile.close();
sequel_execute_literal_update(dbName, "ROLLBACK");
return {SequelResultError, "[react-native-quick-sqlite][loadSQLFile] Unexpected error, transaction was rolledback", 0, 0};
}
}
else
{
return {SequelResultError, "[react-native-quick-sqlite][loadSQLFile] Could not open file", 0, 0};
}
}

jsi::Object createError(jsi::Runtime &rt, string message)
{
auto res = jsi::Object(rt);
Expand Down Expand Up @@ -235,7 +186,7 @@ void install(jsi::Runtime &rt, std::shared_ptr<react::CallInvoker> jsCallInvoker

// Converting results into a JSI Response
auto jsiResult = createSequelQueryExecutionResult(rt, status, &results);
return jsiResult;
return move(jsiResult);
});

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

const string dbName = args[0].asString(rt).utf8(rt);
const jsi::Value &params = args[1];
if (params.isNull() || params.isUndefined())
{
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - An array of SQL commands or parameters is needed");
}
const string dbName = args[0].asString(rt).utf8(rt);
const jsi::Array &batchParams = params.asObject(rt).asArray(rt);
vector<QuickQueryArguments> commands;
jsiBatchParametersToQuickArguments(rt, batchParams, &commands);

auto batchResult = executeBatch(dbName, &commands);
if(batchResult.type == SequelResultOk)
{
auto res = jsi::Object(rt);
res.setProperty(rt, "status", jsi::Value(0));
res.setProperty(rt, "rowsAffected", jsi::Value(batchResult.affectedRows));
return move(res);
} else
{
return createError(rt, batchResult.message);
}
});

auto execSQLBatchAsync = jsi::Function::createFromHostFunction(
rt,
jsi::PropNameID::forAscii(rt, "sequel_execSQLBatchAsync"),
3,
[pool](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
{
if (sizeof(args) < 3)
{
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncExecuteSqlBatch] Incorrect parameter count");
return {};
}

const jsi::Value &params = args[1];
const jsi::Value &callbackHolder = args[2];
if(!callbackHolder.isObject() || !callbackHolder.asObject(rt).isFunction(rt)) {
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncExecuteSqlBatch] The callback argument must be a function");
return {};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, throw error

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EduFrazao throw an error here too please and I'll merge it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ospfranco , its strange, to me its already throwing.
This code looks this way even after ea6de4f ?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, ok, I see it now, but not on this comment, weird.

}

if (params.isNull() || params.isUndefined())
{
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncExecuteSqlBatch] - An array of SQL commands or parameters is needed");
return {};
}

int rowsAffected = 0;
const string dbName = args[0].asString(rt).utf8(rt);
const jsi::Array &batchParams = params.asObject(rt).asArray(rt);
try
auto callback = make_shared<jsi::Value>(callbackHolder.asObject(rt));

vector<QuickQueryArguments> commands;
jsiBatchParametersToQuickArguments(rt, batchParams, &commands);

auto task =
[&rt, dbName, commands = make_shared<vector<QuickQueryArguments>>(commands), callback]()
{
sequel_execute(rt, dbName, "BEGIN TRANSACTION", jsi::Value::undefined());
for (int i = 0; i < batchParams.length(rt); i++)
try
{
const jsi::Array &command = batchParams.getValueAtIndex(rt, i).asObject(rt).asArray(rt);
if (command.length(rt) == 0)
{
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - No SQL Commands found on batch index " + std::to_string(i));
}
const string query = command.getValueAtIndex(rt, 0).asString(rt).utf8(rt);
const jsi::Value &commandParams = command.length(rt) > 1 ? command.getValueAtIndex(rt, 1) : jsi::Value::undefined();
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())
{
// This arguments are an array of arrays, like a batch update of a single sql command.
const jsi::Array &batchUpdateParams = commandParams.asObject(rt).asArray(rt);
for (int x = 0; x < batchUpdateParams.length(rt); x++)
{
const jsi::Value &p = batchUpdateParams.getValueAtIndex(rt, x);
SequelResult result = sequel_execute(rt, dbName, query, p);
if (result.type == SequelResultError)
{
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());
return createError(rt, result.message.c_str());
}
else
{
if (result.value.getObject(rt).hasProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")))
{
rowsAffected += result.value.getObject(rt).getProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")).asNumber();
}
}
}
}
else
// Inside the new worker thread, we can now call sqlite operations
auto batchResult = executeBatch(dbName, commands.get());
invoker->invokeAsync([&rt, batchResult = move(batchResult), callback]
{
SequelResult result = sequel_execute(rt, dbName, query, commandParams);
if (result.type == SequelResultError)
if(batchResult.type == SequelResultOk)
{
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());

return createError(rt, result.message.c_str());
}
else
auto res = jsi::Object(rt);
res.setProperty(rt, "status", jsi::Value(0));
res.setProperty(rt, "rowsAffected", jsi::Value(batchResult.affectedRows));
callback->asObject(rt).asFunction(rt).call(rt, move(res));
} else
{
if (result.value.getObject(rt).hasProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")))
{
rowsAffected += result.value.getObject(rt).getProperty(rt, jsi::PropNameID::forAscii(rt, "rowsAffected")).asNumber();
}
callback->asObject(rt).asFunction(rt).call(rt, createError(rt, batchResult.message));
}
}
});
}
sequel_execute(rt, dbName, "COMMIT", jsi::Value::undefined());
}
catch (...)
{
sequel_execute(rt, dbName, "ROLLBACK", jsi::Value::undefined());
return createError(rt, "[react-native-quick-sqlite][execSQLBatch] - Unexpected error");
}

auto res = jsi::Object(rt);
res.setProperty(rt, "status", jsi::Value(0));
res.setProperty(rt, "rowsAffected", jsi::Value(rowsAffected));
return move(res);
catch (std::exception &exc)
{
invoker->invokeAsync([&rt, callback, &exc]
{ callback->asObject(rt).asFunction(rt).call(rt, createError(rt, exc.what())); });
}
};
pool->queueWork(task);
return {};
});

// Load SQL File from disk
Expand Down Expand Up @@ -359,9 +320,21 @@ void install(jsi::Runtime &rt, std::shared_ptr<react::CallInvoker> jsCallInvoker
3,
[pool](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value
{
if (sizeof(args) < 3)
{
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncLoadSqlFile] Incorrect parameter count");
return {};
}

const jsi::Value &callbackHolder = args[2];
if(!callbackHolder.isObject() || !callbackHolder.asObject(rt).isFunction(rt)) {
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncLoadSqlFile] The callback argument must be a function");
return {};
}

const string dbName = args[0].asString(rt).utf8(rt);
const string sqlFileName = args[1].asString(rt).utf8(rt);
auto callback = make_shared<jsi::Value>((args[2].asObject(rt)));
auto callback = make_shared<jsi::Value>(callbackHolder.asObject(rt));

auto task =
[&rt, dbName, sqlFileName, callback]()
Expand Down Expand Up @@ -405,14 +378,20 @@ void install(jsi::Runtime &rt, std::shared_ptr<react::CallInvoker> jsCallInvoker
{
if (count < 4)
{
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite] Incorrect arguments for asyncExecuteSQL");
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncExecuteSql] Incorrect arguments for asyncExecuteSQL");
return {};
}

const jsi::Value &callbackHolder = args[3];
if(!callbackHolder.isObject() || !callbackHolder.asObject(rt).isFunction(rt)) {
jsi::detail::throwJSError(rt, "[react-native-quick-sqlite][asyncExecuteSql] The callback argument must be a function");
return {};
}

const string dbName = args[0].asString(rt).utf8(rt);
const string query = args[1].asString(rt).utf8(rt);
const jsi::Value &originalParams = args[2];
auto callback = make_shared<jsi::Value>(args[3].asObject(rt));
auto callback = make_shared<jsi::Value>(callbackHolder.asObject(rt));

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

Expand Down
89 changes: 89 additions & 0 deletions cpp/sqlbatchexecutor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* Batch execution implementation
*/
#include "sqlbatchexecutor.h"

void jsiBatchParametersToQuickArguments(jsi::Runtime &rt, jsi::Array const &batchParams, vector<QuickQueryArguments> *commands)
{
for (int i = 0; i < batchParams.length(rt); i++)
{
const jsi::Array &command = batchParams.getValueAtIndex(rt, i).asObject(rt).asArray(rt);
if (command.length(rt) == 0)
{
continue;
}

const string query = command.getValueAtIndex(rt, 0).asString(rt).utf8(rt);
const jsi::Value &commandParams = command.length(rt) > 1 ? command.getValueAtIndex(rt, 1) : jsi::Value::undefined();
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())
{
// This arguments is an array of arrays, like a batch update of a single sql command.
const jsi::Array &batchUpdateParams = commandParams.asObject(rt).asArray(rt);
for (int x = 0; x < batchUpdateParams.length(rt); x++)
{
const jsi::Value &p = batchUpdateParams.getValueAtIndex(rt, x);
vector<QuickValue> params;
jsiQueryArgumentsToSequelParam(rt, p, &params);
commands->push_back(QuickQueryArguments{
query,
make_shared<vector<QuickValue>>(params)
});
}
}
else
{
vector<QuickValue> params;
jsiQueryArgumentsToSequelParam(rt, commandParams, &params);
commands->push_back(QuickQueryArguments{
query,
make_shared<vector<QuickValue>>(params)
});
}
}
}

SequelBatchOperationResult executeBatch(std::string dbName, vector<QuickQueryArguments> *commands)
{
size_t commandCount = commands->size();
if(commandCount <= 0)
{
return SequelBatchOperationResult {
.type = SequelResultError,
.message = "No SQL commands provided",
};
}

try
{
int affectedRows = 0;
sequel_execute_literal_update(dbName, "BEGIN EXCLUSIVE TRANSACTION");
for(int i = 0; i<commandCount; i++) {
auto command = commands->at(i);
// We do not provide a datastructure to receive query data because we don't need/want to handle this results in a batch execution
auto result = sequel_execute3(dbName, command.sql, command.params.get(), NULL);
if(result.type == SequelResultError)
{
return SequelBatchOperationResult {
.type = SequelResultError,
.message = result.errorMessage,
};
} else
{
affectedRows += result.rowsAffected;
}
}
sequel_execute_literal_update(dbName, "COMMIT");
return SequelBatchOperationResult {
.type = SequelResultOk,
.affectedRows = affectedRows,
.commands = (int) commandCount,
};
} catch(std::exception &exc)
{
sequel_execute_literal_update(dbName, "ROLLBACK");
return SequelBatchOperationResult {
.type = SequelResultError,
.message = exc.what(),
};
}
}
Loading