diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index 0f40102..3055c6b 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -46,6 +46,10 @@ add_library( ../cpp/react-native-quick-sqlite.h ../cpp/sqlite3.h ../cpp/sqlite3.c + ../cpp/JSIHelper.h + ../cpp/JSIHelper.cpp + ../cpp/ThreadPool.h + ../cpp/ThreadPool.cpp cpp-adapter.cpp ) diff --git a/cpp/JSIHelper.cpp b/cpp/JSIHelper.cpp index 8b61fa7..436d7af 100644 --- a/cpp/JSIHelper.cpp +++ b/cpp/JSIHelper.cpp @@ -10,70 +10,187 @@ using namespace std; using namespace facebook; -vector jsiArrayToVector(jsi::Runtime &rt, jsi::Array values) +SequelValue createNullSequelValue() { + return SequelValue { + .dataType = NULL_VALUE + }; +} + +SequelValue createBooleanSequelValue(bool value) { + return SequelValue { + .dataType = BOOLEAN, + .booleanValue = int(value) + }; +} + +SequelValue createTextSequelValue(string value) { + return SequelValue { + .dataType = TEXT, + .textValue = value + }; +} + +SequelValue createIntegerSequelValue(int value) { + return SequelValue { + .dataType = INTEGER, + .doubleOrIntValue = static_cast(value) + }; +} + +SequelValue createIntegerSequelValue(double value) { + return SequelValue { + .dataType = INTEGER, + .doubleOrIntValue = value + }; +} + +SequelValue createInt64SequelValue(long long value) { + return SequelValue { + .dataType = INT64, + .int64Value = value + }; +} + +SequelValue createDoubleSequelValue(double value) { + return SequelValue { + .dataType = DOUBLE, + .doubleOrIntValue = value + }; +} + +SequelValue createArrayBufferSequelValue(uint8_t *arrayBufferValue, size_t arrayBufferSize) { + return SequelValue { + .dataType = ARRAY_BUFFER, + .arrayBufferValue = shared_ptr{arrayBufferValue}, + .arrayBufferSize = arrayBufferSize + }; +} + +void jsiQueryArgumentsToSequelParam(jsi::Runtime &rt, jsi::Value const ¶ms, vector *target) { -// int jsiParamsLength = params.length(rt); -// vector res; -// for (int ii = 0; ii < jsiParamsLength; ii++) -// { -// res.push_back(params.getValueAtIndex(rt, ii).asString(rt).utf8(rt)); -// } -// return res; - vector res; - + if (params.isNull() || params.isUndefined()) + { + return; + } + + jsi::Array values = params.asObject(rt).asArray(rt); + for (int ii = 0; ii < values.length(rt); ii++) { + jsi::Value value = values.getValueAtIndex(rt, ii); - if (value.isNull()) + if (value.isNull() || value.isUndefined()) { - res.push_back(nullptr); -// sqlite3_bind_null(statement, ii + 1); + target->push_back(createNullSequelValue()); } else if (value.isBool()) { - res.push_back(value.getBool()); -// int intVal = int(value.getBool()); -// sqlite3_bind_int(statement, ii + 1, intVal); + int intVal = int(value.getBool()); + target->push_back(createBooleanSequelValue(value.getBool())); } else if (value.isNumber()) { - res.push_back(value.asNumber()); -// double doubleVal = value.asNumber(); -// int intVal = (int)doubleVal; -// long long longVal = (long)doubleVal; -// if (intVal == doubleVal) -// { -// sqlite3_bind_int(statement, ii + 1, intVal); -// } -// else if (longVal == doubleVal) -// { -// sqlite3_bind_int64(statement, ii + 1, longVal); -// } -// else -// { -// sqlite3_bind_double(statement, ii + 1, doubleVal); -// } + double doubleVal = value.asNumber(); + int intVal = (int)doubleVal; + long long longVal = (long)doubleVal; + if (intVal == doubleVal) + { + target->push_back(createIntegerSequelValue(intVal)); + } + else if (longVal == doubleVal) + { + target->push_back(createInt64SequelValue(longVal)); + } + else + { + target->push_back(createDoubleSequelValue(doubleVal)); + } } else if (value.isString()) { - res.push_back(value.asString(rt).utf8(rt)); -// string strVal = value.asString(rt).utf8(rt); -// -// sqlite3_bind_text(statement, ii + 1, strVal.c_str(), strVal.length(), SQLITE_TRANSIENT); + string strVal = value.asString(rt).utf8(rt); + target->push_back(createTextSequelValue(strVal)); } else if (value.isObject()) { -// auto obj = value.asObject(rt); -// if (obj.isArrayBuffer(rt)) -// { -// auto buf = obj.getArrayBuffer(rt); -// // The statement is executed before returning control to JSI, so we don't need to copy the data to extend its lifetime. -// sqlite3_bind_blob(statement, ii + 1, buf.data(rt), buf.size(rt), SQLITE_STATIC); -// } + auto obj = value.asObject(rt); + if (obj.isArrayBuffer(rt)) + { + auto buf = obj.getArrayBuffer(rt); + target->push_back(createArrayBufferSequelValue(buf.data(rt), buf.size(rt))); + } + } + else { + target->push_back(createNullSequelValue()); } } - - - - return res; } + +jsi::Value createSequelQueryExecutionResult(jsi::Runtime &rt, SequelOperationStatus status, vector> *results) +{ + jsi::Object res = jsi::Object(rt); + if(status.type == SequelResultOk) + { + //res.setProperty(rt, "rows", move(rows)); + res.setProperty(rt, "rowsAffected", jsi::Value(status.rowsAffected)); + if (status.rowsAffected > 0 && status.insertId != 0) + { + res.setProperty(rt, "insertId", jsi::Value(status.insertId)); + } + + // Converting row results into objects + size_t rowCount = results->size(); + jsi::Object rows = jsi::Object(rt); + if(rowCount > 0) + { + auto array = jsi::Array(rt, rowCount); + for(int i = 0; iat(i); + for (auto const& entry : row) + { + std::string columnName = entry.first; + SequelValue value = entry.second; + if (value.dataType == TEXT) + { + rowObject.setProperty(rt, columnName.c_str(), jsi::String::createFromUtf8(rt, value.textValue.c_str())); + } + else if (value.dataType == INTEGER) + { + rowObject.setProperty(rt, columnName.c_str(), jsi::Value(value.doubleOrIntValue)); + } + else if (value.dataType == DOUBLE) + { + rowObject.setProperty(rt, columnName.c_str(), jsi::Value(value.doubleOrIntValue)); + } + else if (value.dataType == ARRAY_BUFFER) + { + jsi::Function array_buffer_ctor = rt.global().getPropertyAsFunction(rt, "ArrayBuffer"); + jsi::Object o = array_buffer_ctor.callAsConstructor(rt, (int)value.arrayBufferSize).getObject(rt); + jsi::ArrayBuffer buf = o.getArrayBuffer(rt); + // It's a shame we have to copy here: see https://github.com/facebook/hermes/pull/419 and https://github.com/facebook/hermes/issues/564. + memcpy(buf.data(rt), value.arrayBufferValue.get(), value.arrayBufferSize); + rowObject.setProperty(rt, columnName.c_str(), o); + } + else + { + rowObject.setProperty(rt, columnName.c_str(), jsi::Value(nullptr)); + } + } + array.setValueAtIndex(rt, i, move(rowObject)); + } + rows.setProperty(rt, "_array", move(array)); + res.setProperty(rt, "rows", move(rows)); + } + rows.setProperty(rt, "status", jsi::Value(0)); + rows.setProperty(rt, "length", jsi::Value((int)rowCount)); + } + else + { + res.setProperty(rt, "status", jsi::Value(1)); + res.setProperty(rt, "message", jsi::String::createFromUtf8(rt, status.errorMessage.c_str())); + } + + return move(res); +} \ No newline at end of file diff --git a/cpp/JSIHelper.h b/cpp/JSIHelper.h index 6af085d..20482ba 100644 --- a/cpp/JSIHelper.h +++ b/cpp/JSIHelper.h @@ -11,11 +11,27 @@ #include #include #include -#include +#include +#include +#include "SequelResult.h" using namespace std; using namespace facebook; -vector jsiArrayToVector(jsi::Runtime &rt, jsi::Array values); +/** + * Fill the target vector with parsed parameters + * */ +void jsiQueryArgumentsToSequelParam(jsi::Runtime &rt, jsi::Value const &args, vector *target); + +SequelValue createNullSequelValue(); +SequelValue createBooleanSequelValue(bool value); +SequelValue createTextSequelValue(string value); +SequelValue createIntegerSequelValue(int value); +SequelValue createIntegerSequelValue(double value); +SequelValue createInt64SequelValue(long long value); +SequelValue createDoubleSequelValue(double value); +SequelValue createArrayBufferSequelValue(uint8_t *arrayBufferValue, size_t arrayBufferSize); +jsi::Value createSequelQueryExecutionResult(jsi::Runtime &rt, SequelOperationStatus status, vector> *results); + #endif /* JSIHelper_h */ diff --git a/cpp/SequelResult.h b/cpp/SequelResult.h index 7cd475b..56a80f6 100644 --- a/cpp/SequelResult.h +++ b/cpp/SequelResult.h @@ -12,11 +12,41 @@ #include #include #include -#include using namespace std; using namespace facebook; +enum SequelDataType { + NULL_VALUE, + TEXT, + INTEGER, + INT64, + DOUBLE, + BOOLEAN, + ARRAY_BUFFER, +}; + +/** + * TypeSafe dynamic parameter value to bind on sqlite statements + */ +struct SequelValue { + SequelDataType dataType; + int booleanValue; + double doubleOrIntValue; + long long int64Value; + string textValue; + shared_ptr arrayBufferValue; + size_t arrayBufferSize; +}; + +/** + * TypeSafe dynamic column value holder representing a sqlite columnValue + */ +struct SequelColumnValue { + SequelValue value; + string columnName; +}; + enum ResultType { SequelResultOk, @@ -30,6 +60,14 @@ struct SequelResult jsi::Value value; }; +struct SequelOperationStatus +{ + ResultType type; + string errorMessage; + int rowsAffected; + double insertId; +}; + struct SequelLiteralUpdateResult { ResultType type; @@ -45,8 +83,3 @@ struct SequelBatchOperationResult int commands; }; -struct SQLiteValueWrapper -{ - string name; - any value; -}; diff --git a/cpp/react-native-quick-sqlite.cpp b/cpp/react-native-quick-sqlite.cpp index 59193d1..455c854 100644 --- a/cpp/react-native-quick-sqlite.cpp +++ b/cpp/react-native-quick-sqlite.cpp @@ -227,55 +227,18 @@ void installSequel(jsi::Runtime &rt, std::shared_ptr jsCallI { const string dbName = args[0].asString(rt).utf8(rt); const string query = args[1].asString(rt).utf8(rt); - const vector params = jsiArrayToVector(rt, args[2].asObject(rt).asArray(rt)); - -// LOGW("VECTOR CREATED"); -// std::cout << std::boolalpha; - -// for(auto i: params) { - -// LOGW("PARAM: %s", i.type().name()); -// std::cout << "PARAM: " << i.type().name() << endl; -// } - - auto rows = sequel_execute2(dbName, query, params); - - auto array = jsi::Array(rt, rows.size()); - - for (int i = 0; i < rows.size(); i++) - { -// LOGW("ITERATE %d", i); - auto row = rows[i]; - - auto jsiRow = jsi::Object(rt); - - for(SQLiteValueWrapper rowInfo: row) { - const char* typeName = rowInfo.value.type().name(); - LOGW("TYPENAME %s", typeName) - if(strcmp(typeName, "d") == 0) { - jsiRow.setProperty(rt, rowInfo.name.c_str(), jsi::Value(rt, any_cast(rowInfo.value))); - } else if(strcmp(typeName, "NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE") == 0) { -// jsi::String::createFromUtf8(rt, column_value) - jsiRow.setProperty(rt, rowInfo.name.c_str(), jsi::String::createFromUtf8(rt, any_cast(rowInfo.value).c_str())); - } - - } - array.setValueAtIndex(rt, i, jsiRow); - } - - return array; - - -// const jsi::Value ¶ms = args[2]; -// SequelResult result = sequel_execute(rt, dbName, query, params); -// -// if (result.type == SequelResultError) -// { -// return createError(rt, result.message.c_str()); -// } -// -// return move(result.value); - return {}; + const jsi::Value &originalParams = args[2]; + // Converting parameters + vector params; + jsiQueryArgumentsToSequelParam(rt, originalParams, ¶ms); + + // Filling the results + vector> results; + auto status = sequel_execute3(dbName, query, ¶ms, &results); + + // Converting results into a JSI Response + auto jsiResult = createSequelQueryExecutionResult(rt, status, &results); + return move(jsiResult); }); // Execute a batch of SQL queries in a transaction @@ -449,28 +412,27 @@ void installSequel(jsi::Runtime &rt, std::shared_ptr jsCallI } const string dbName = args[0].asString(rt).utf8(rt); const string query = args[1].asString(rt).utf8(rt); - auto params = make_shared(args[2].asObject(rt)); + const jsi::Value &originalParams = args[2]; auto callback = make_shared(args[3].asObject(rt)); + // Converting query parameters inside the javascript caller thread + vector params; + jsiQueryArgumentsToSequelParam(rt, originalParams, ¶ms); + auto task = - [&rt, dbName, query, params, callback]() + [&rt, dbName, query, params=make_shared>(params), callback]() { try { - invoker->invokeAsync([&rt, callback] { - callback->asObject(rt).asFunction(rt).call(rt, jsi::Value(rt, 4)); + // Inside the new worker thread, we can now call sqlite operations + vector> results; + auto status = sequel_execute3(dbName, query, params.get(), &results); + invoker->invokeAsync([&rt, results=make_shared>>(results), status_copy = move(status), callback] { + // Now, back into the JavaScript thread, we can translate the results + // back to a JSI Object to pass on the callback + auto jsiResult = createSequelQueryExecutionResult(rt, status_copy, results.get()); + callback->asObject(rt).asFunction(rt).call(rt, move(jsiResult)); }); -// SequelResult result = sequel_execute(rt, dbName, query, *params); -// if (result.type == SequelResultError) -// { -// invoker->invokeAsync([&rt, callback, &result] -// { callback->asObject(rt).asFunction(rt).call(rt, createError(rt, result.message.c_str())); }); -// } -// else -// { -// invoker->invokeAsync([&rt, callback, &result] -// { callback->asObject(rt).asFunction(rt).call(rt, result.value); }); -// } } catch (std::exception &exc) { diff --git a/cpp/sequel.cpp b/cpp/sequel.cpp index 11c073b..0f514ba 100644 --- a/cpp/sequel.cpp +++ b/cpp/sequel.cpp @@ -184,48 +184,6 @@ SequelResult sequel_remove(string const dbName, string const docPath) jsi::Value::undefined()}; } -void bindStatement2(sqlite3_stmt *statement, const vector ¶ms) -{ - try - { - for (int ii = 0; ii < params.size(); ii++) - { - any item = params[ii]; - const char *typeName = item.type().name(); - - if (strcmp(typeName, "d") == 0) - { - double doubleVal = any_cast(item); - int intVal = (int)doubleVal; - long long longVal = (long)doubleVal; - if (intVal == doubleVal) - { - sqlite3_bind_int(statement, ii + 1, intVal); - } - else if (longVal == doubleVal) - { - sqlite3_bind_int64(statement, ii + 1, longVal); - } - else - { - sqlite3_bind_double(statement, ii + 1, doubleVal); - } - } - else if(strcmp(typeName, "NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE") == 0) { - string strVal = any_cast(item); - sqlite3_bind_text(statement, ii + 1, strVal.c_str(), strVal.length(), SQLITE_TRANSIENT); - } else - { - LOGW("UNKNOWN TYPE DETECTED %s", typeName); - } - } - } - catch (const std::bad_any_cast &e) - { - std::cout << e.what() << '\n'; - } -} - void bindStatement(sqlite3_stmt *statement, jsi::Runtime &rt, jsi::Value const ¶ms) { if (params.isNull() || params.isUndefined()) @@ -285,15 +243,57 @@ void bindStatement(sqlite3_stmt *statement, jsi::Runtime &rt, jsi::Value const & } } -vector> sequel_execute2(const string dbName, const string &query, const vector ¶ms) +// Using Structs IMPL +void bindStatementWithValues(sqlite3_stmt *statement, vector *values) +{ + size_t size = values->size(); + if (size <= 0) + { + return; + } + + for (int ii = 0; ii < size; ii++) + { + int sqIndex = ii + 1; + SequelValue value = values->at(ii); + SequelDataType dataType = value.dataType; + if (dataType == NULL_VALUE) + { + sqlite3_bind_null(statement, sqIndex); + } + else if (dataType == BOOLEAN) + { + sqlite3_bind_int(statement, sqIndex, value.booleanValue); + } + else if (dataType == INTEGER) + { + sqlite3_bind_int(statement, sqIndex, (int) value.doubleOrIntValue); + } else if (dataType == DOUBLE) + { + sqlite3_bind_double(statement, sqIndex, value.doubleOrIntValue); + } else if (dataType == INT64) + { + sqlite3_bind_int64(statement, sqIndex, value.int64Value); + } else if (dataType == TEXT) + { + sqlite3_bind_text(statement, sqIndex, value.textValue.c_str(), value.textValue.length(), SQLITE_TRANSIENT); + } else if (dataType == ARRAY_BUFFER) + { + sqlite3_bind_blob(statement, sqIndex, value.arrayBufferValue.get(), value.arrayBufferSize, SQLITE_STATIC); + } + } +} + +SequelOperationStatus sequel_execute3(string const dbName, string const &query, vector *params, vector> *results) { // Check if db connection is opened if (dbMap.count(dbName) == 0) { - LOGE("QUICKSQLITE EXEC2 NO DATABASE FOUND"); - // return SequelResult{ - // SequelResultError, - // "[react-native-quick-sqlite]: Database " + dbName + " is not open"}; + return SequelOperationStatus{ + SequelResultError, + "[react-native-quick-sqlite]: Database " + dbName + " is not open", + 0 + }; } sqlite3 *db = dbMap[dbName]; @@ -306,31 +306,34 @@ vector> sequel_execute2(const string dbName, const st if (statementStatus == SQLITE_OK) // statemnet is correct, bind the passed parameters { - // bindStatement(statement, rt, params); - bindStatement2(statement, params); + bindStatementWithValues(statement, params); } else { const char *message = sqlite3_errmsg(db); - LOGE("QUICKSQLITE EXEC2 FAILED: %s", message); + return SequelOperationStatus { + SequelResultError, + "[react-native-quick-sqlite] SQL execution error: " + string(message), + 0 + }; } - + bool isConsuming = true; bool isFailed = false; int result, i, count, column_type; string column_name; - vector> rows; - + map row; + while (isConsuming) { result = sqlite3_step(statement); - vector row; switch (result) { case SQLITE_ROW: i = 0; + row = map(); count = sqlite3_column_count(statement); while (i < count) @@ -351,64 +354,45 @@ vector> sequel_execute2(const string dbName, const st * See https://github.com/ospfranco/react-native-quick-sqlite/issues/16 for more context. */ double column_value = sqlite3_column_double(statement, i); - row.push_back({ - column_name.c_str(), - column_value - }); + row[column_name] = createIntegerSequelValue(column_value); break; } case SQLITE_FLOAT: { double column_value = sqlite3_column_double(statement, i); - row.push_back({ - column_name.c_str(), - column_value - }); + row[column_name] = createDoubleSequelValue(column_value); break; } case SQLITE_TEXT: { const char *column_value = reinterpret_cast(sqlite3_column_text(statement, i)); - row.push_back({ - column_name.c_str(), - string(column_value) - }); -// entry.setProperty(rt, column_name.c_str(), jsi::String::createFromUtf8(rt, column_value)); + row[column_name] = createTextSequelValue(string(column_value)); break; } case SQLITE_BLOB: { -// int blob_size = sqlite3_column_bytes(statement, i); -// const void *blob = sqlite3_column_blob(statement, i); -// jsi::Function array_buffer_ctor = rt.global().getPropertyAsFunction(rt, "ArrayBuffer"); -// jsi::Object o = array_buffer_ctor.callAsConstructor(rt, blob_size).getObject(rt); -// jsi::ArrayBuffer buf = o.getArrayBuffer(rt); -// // It's a shame we have to copy here: see https://github.com/facebook/hermes/pull/419 and https://github.com/facebook/hermes/issues/564. -// memcpy(buf.data(rt), blob, blob_size); -// entry.setProperty(rt, column_name.c_str(), o); -// break; + int blob_size = sqlite3_column_bytes(statement, i); + const void *blob = sqlite3_column_blob(statement, i); + uint8_t *data; + memcpy(data, blob, blob_size); + row[column_name] = createArrayBufferSequelValue(data, blob_size); + break; } case SQLITE_NULL: // Intentionally left blank to switch to default case default: -// entry.setProperty(rt, column_name.c_str(), jsi::Value(nullptr)); - row.push_back({ - column_name.c_str(), - nullptr - }); + row[column_name] = createNullSequelValue(); break; } i++; } - - rows.push_back(row); + results->push_back(move(row)); break; - case SQLITE_DONE: isConsuming = false; break; @@ -424,65 +408,22 @@ vector> sequel_execute2(const string dbName, const st if (isFailed) { const char *message = sqlite3_errmsg(db); -// return { -// SequelResultError, -// "[react-native-quick-sqlite] SQL execution error: " + string(message), -// jsi::Value::undefined()}; + return SequelOperationStatus{ + SequelResultError, + "[react-native-quick-sqlite] SQL execution error: " + string(message), + 0, + 0 + }; } - - LOGW("STATEMENT RETRIEVED!"); - - return rows; - // Move everything into a JSI object -// auto array = jsi::Array(rt, results.size()); -// for (int i = 0; i < results.size(); i++) -// { -// array.setValueAtIndex(rt, i, move(results[i])); -// } -// -// jsi::Object rows = jsi::Object(rt); -// rows.setProperty(rt, "status", jsi::Value(0)); -// rows.setProperty(rt, "length", jsi::Value((int)results.size())); -// rows.setProperty(rt, "_array", move(array)); -// -// // For any future endaevors, I tried to create the accesor function directly on via JSI -// // But this is too complex for my punny brain, so this function is created on the index.ts file -// // // Create accessor function -// // auto itemAccesser = jsi::Function::createFromHostFunction( -// // rt, -// // jsi::PropNameID::forAscii(rt, "item"), -// // 1, -// // [&array](jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) -> jsi::Value -// // { -// // if(args[0].isNumber()) { -// // double rowNumber = args[0].asNumber(); -// // cout << "trying to access value at" << rowNumber << endl; -// // return array.getValueAtIndex(rt, (int)rowNumber); -// // } -// // -// // return {}; -// // }); -// -// // rows.setProperty(rt, "item", move(itemAccesser)); -// -// jsi::Object res = jsi::Object(rt); -// res.setProperty(rt, "rows", move(rows)); -// -// int changedRowCount = sqlite3_changes(db); -// res.setProperty(rt, "rowsAffected", jsi::Value(changedRowCount)); -// -// // row id has nothing to do with the actual uuid/id of the object, but internal row count -// long long latestInsertRowId = sqlite3_last_insert_rowid(db); -// if (changedRowCount > 0 && latestInsertRowId != 0) -// { -// res.setProperty(rt, "insertId", jsi::Value((int)latestInsertRowId)); -// } -// -// return { -// SequelResultOk, -// "", -// move(res)}; + int changedRowCount = sqlite3_changes(db); + long long latestInsertRowId = sqlite3_last_insert_rowid(db); + return SequelOperationStatus{ + SequelResultOk, + "", + changedRowCount, + static_cast(latestInsertRowId) + }; } SequelResult sequel_execute(jsi::Runtime &rt, string const dbName, string const &query, jsi::Value const ¶ms) diff --git a/cpp/sequel.h b/cpp/sequel.h index 6ae3c18..2d85ef5 100644 --- a/cpp/sequel.h +++ b/cpp/sequel.h @@ -10,8 +10,8 @@ #include #include #include "SequelResult.h" +#include "JSIHelper.h" #include -#include using namespace std; using namespace facebook; @@ -26,6 +26,6 @@ SequelResult sequel_remove(string const dbName, string const docPath); SequelResult sequel_execute(jsi::Runtime &rt, string const dbName, string const &query, jsi::Value const ¶ms); -vector> sequel_execute2(string const dbName, string const &query, const vector ¶ms); +SequelOperationStatus sequel_execute3(string const dbName, string const &query, vector *values, vector> *result); SequelLiteralUpdateResult sequel_execute_literal_update(string const dbName, string const &query);