#include "DatabaseAPI.h" using namespace DB; #define CATCH_AND_THROW(LOG) \ catch (const Exception& e) { \ logger.error(LOG##"\n"); \ PrintException(e); \ logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); \ return Local(); \ } \ catch (const std::exception& e) { \ throw Exception(TextEncoding::toUTF8(e.what())); \ } \ catch (const seh_exception& e) { \ logger.error("SEH Uncaught Exception Detected!"); \ logger.error(TextEncoding::toUTF8(e.what())); \ PrintScriptStackTrace(); \ logger.error(std::string("In API: ") + __FUNCTION__); \ logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); \ return Local(); \ } \ catch (...) { \ logger.error("Uncaught Exception Detected!"); \ PrintScriptStackTrace(); \ logger.error(std::string("In API: ") + __FUNCTION__); \ logger.error("In Plugin: " + ENGINE_OWN_DATA()->pluginName); \ return Local(); \ } //////////////////// Class Definition //////////////////// ClassDefine KVDBClassBuilder = defineClass("KVDatabase") .constructor(&KVDBClass::constructor) .instanceFunction("get", &KVDBClass::get) .instanceFunction("set", &KVDBClass::set) .instanceFunction("delete", &KVDBClass::del) .instanceFunction("close", &KVDBClass::close) .instanceFunction("listKey", &KVDBClass::listKey) .build(); ClassDefine DBSessionClassBuilder = defineClass("DBSession") .constructor(&DBSessionClass::constructor) .instanceFunction("query", &DBSessionClass::query) .instanceFunction("exec", &DBSessionClass::exec) .instanceFunction("execute", &DBSessionClass::exec) .instanceFunction("prepare", &DBSessionClass::prepare) .instanceFunction("close", &DBSessionClass::close) .instanceFunction("isOpen", &DBSessionClass::isOpen) .build(); ClassDefine DBStmtClassBuilder = defineClass("DBStmt") .constructor(nullptr) .instanceProperty("affectedRows", &DBStmtClass::getAffectedRows) .instanceProperty("insertId", &DBStmtClass::getInsertId) .instanceFunction("bind", &DBStmtClass::bind) .instanceFunction("execute", &DBStmtClass::execute) .instanceFunction("step", &DBStmtClass::step) .instanceFunction("fetch", &DBStmtClass::fetch) .instanceFunction("fetchAll", &DBStmtClass::fetchAll) .instanceFunction("reset", &DBStmtClass::reset) .instanceFunction("reexec", &DBStmtClass::reexec) .instanceFunction("clear", &DBStmtClass::clear) .build(); //////////////////// Functions //////////////////// Any LocalValueToAny(const Local& val) { switch (val.getKind()) { case ValueKind::kObject: case ValueKind::kArray: throw std::exception("Cannot convert script object(array) to Any"); case ValueKind::kNull: case ValueKind::kUnsupported: return Any(); case ValueKind::kBoolean: return Any(val.asBoolean().value()); case ValueKind::kNumber: if (CheckIsFloat(val.asNumber())) return Any(val.asNumber().toDouble()); else return Any(val.asNumber().toInt64()); case ValueKind::kString: return Any(val.asString().toString()); case ValueKind::kByteBuffer: switch (val.asByteBuffer().getType()) { case ByteBuffer::Type::kInt8: case ByteBuffer::Type::kUint8: { auto buf = (uint8_t*)val.asByteBuffer().getRawBytes(); return Any(ByteArray(buf, buf + val.asByteBuffer().elementCount())); } default: break; } break; default: break; } return Any(); } template <> Local any_to(const Any& val) { switch (val.type) { case Any::Type::Null: return Local(); case Any::Type::Boolean: return Boolean::newBoolean(val.value.boolean); case Any::Type::Integer: return Number::newNumber(val.value.integer); case Any::Type::UInteger: if (val.value.uinteger > LLONG_MAX) return Number::newNumber((double)val.value.uinteger); return Number::newNumber((int64_t)val.value.uinteger); case Any::Type::Floating: return Number::newNumber(val.value.floating); case Any::Type::String: return String::newString(*val.value.string); case Any::Type::Date: { auto obj = Object::newObject(); obj.set("Y", val.value.date->year); obj.set("M", val.value.date->month); obj.set("D", val.value.date->day); return obj; } case Any::Type::Time: { auto obj = Object::newObject(); obj.set("h", val.value.time->hour); obj.set("m", val.value.time->minute); obj.set("s", val.value.time->second); return obj; } case Any::Type::DateTime: { auto obj = Object::newObject(); obj.set("Y", val.value.datetime->date.year); obj.set("M", val.value.datetime->date.month); obj.set("D", val.value.datetime->date.day); obj.set("h", val.value.datetime->time.hour); obj.set("m", val.value.datetime->time.minute); obj.set("s", val.value.datetime->time.second); return obj; } case Any::Type::Blob: return String::newString(val.get()); default: break; } return Local(); } Local RowSetToLocalValue(const RowSet& rows) { if (rows.empty() || !rows.header) { return Local(); } Local arr = Array::newArray(); Local header = Array::newArray(); for (auto& col : *rows.header) header.add(String::newString(col)); arr.add(header); for (auto& row : rows) { Local rowValues = Array::newArray(); for (auto& col : row) rowValues.add(col.get>()); arr.add(rowValues); } return arr; } Local RowToLocalValue(const Row& row) { auto result = Object::newObject(); row.forEach([&](const std::string& key, const Any& value) { result.set(key, value.get>()); return true; }); return result; } //////////////////// Classes KVDB //////////////////// // 生成函数 KVDBClass::KVDBClass(const Local& scriptObj, const string& dir) : ScriptClass(scriptObj) , kvdb(KVDB::create(dir)) { unloadCallbackIndex = ENGINE_OWN_DATA()->addUnloadCallback([&](ScriptEngine* engine) { kvdb.reset(); }); } KVDBClass::KVDBClass(const string& dir) : ScriptClass(script::ScriptClass::ConstructFromCpp{}) , kvdb(KVDB::create(dir)) { unloadCallbackIndex = ENGINE_OWN_DATA()->addUnloadCallback([&](ScriptEngine* engine) { kvdb.reset(); }); } KVDBClass::~KVDBClass() { } KVDBClass* KVDBClass::constructor(const Arguments& args) { CHECK_ARGS_COUNT_C(args, 1); CHECK_ARG_TYPE_C(args[0], ValueKind::kString); try { auto res = new KVDBClass(args.thiz(), args[0].toStr()); if (res->isValid()) return res; else return nullptr; } CATCH_C("Fail in Open Database!"); } Local KVDBClass::get(const Arguments& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kString); try { if (!isValid()) return Local(); string res; if (!kvdb->get(args[0].asString().toString(), res)) return Local(); return JsonToValue(res); } CATCH_AND_THROW("Fail in DbGet!"); } Local KVDBClass::set(const Arguments& args) { CHECK_ARGS_COUNT(args, 2); CHECK_ARG_TYPE(args[0], ValueKind::kString); try { if (!isValid()) return Local(); kvdb->put(args[0].asString().toString(), ValueToJson(args[1])); return Boolean::newBoolean(true); } CATCH_AND_THROW("Fail in DbSet!"); } Local KVDBClass::del(const Arguments& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kString); try { if (!isValid()) return Local(); return Boolean::newBoolean(kvdb->del(args[0].asString().toString())); } CATCH_AND_THROW("Fail in DbDel!"); } Local KVDBClass::close(const Arguments& args) { ENGINE_OWN_DATA()->removeUnloadCallback(unloadCallbackIndex); unloadCallbackIndex = -1; try { kvdb.reset(); return Boolean::newBoolean(true); } CATCH_AND_THROW("Fail in DbClose!"); } Local KVDBClass::listKey(const Arguments& args) { try { if (!isValid()) return Local(); auto list = kvdb->getAllKeys(); Local arr = Array::newArray(); for (auto& key : list) { arr.add(String::newString(key)); } return arr; } CATCH_AND_THROW("Fail in DbListKey!"); } //////////////////// Classes DBSession //////////////////// // 生成函数 DBSessionClass::DBSessionClass(const Local& scriptObj, const ConnParams& params) : ScriptClass(scriptObj) , session(Session::create(params)) { session->setDebugOutput(true); } DBSessionClass::DBSessionClass(const ConnParams& params) : ScriptClass(script::ScriptClass::ConstructFromCpp{}) , session(Session::create(params)) { session->setDebugOutput(true); } DBSessionClass::~DBSessionClass() { } DBSessionClass* DBSessionClass::constructor(const Arguments& args) { try { DBSessionClass* result = nullptr; switch (args.size()) { case 1: { // When the first argument is a string, it is a url or file path. if (args[0].isString()) { result = new DBSessionClass(args.thiz(), ConnParams(args[0].asString().toString())); } else if (args[0].isObject()) { auto obj = args[0].asObject(); ConnParams params; for (auto& key : obj.getKeys()) params[key.toString()] = LocalValueToAny(obj.get(key)); result = new DBSessionClass(args.thiz(), params); } else { LOG_WRONG_ARG_TYPE(); } break; } case 2: { CHECK_ARG_TYPE_C(args[0], ValueKind::kString); CHECK_ARG_TYPE_C(args[1], ValueKind::kObject); auto obj = args[1].asObject(); ConnParams params; params["type"] = args[0].asString().toString(); for (auto& key : obj.getKeys()) params[key.toString()] = LocalValueToAny(obj.get(key)); result = new DBSessionClass(args.thiz(), ConnParams(params)); break; } default: LOG_WRONG_ARG_TYPE(); break; } return result; } CATCH_C("Fail in Open Database!"); } Local DBSessionClass::query(const Arguments& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kString); try { auto res = session->query(args[0].asString().toString()); return RowSetToLocalValue(res); } CATCH_AND_THROW("Fail in query!"); } Local DBSessionClass::exec(const Arguments& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kString); try { session->execute(args[0].asString().toString()); return this->getScriptObject(); } CATCH_AND_THROW("Fail in exec!"); } Local DBSessionClass::prepare(const Arguments& args) { CHECK_ARGS_COUNT(args, 1); CHECK_ARG_TYPE(args[0], ValueKind::kString); try { auto stmt = new DBStmtClass(session->prepare(args[0].asString().toString())); return stmt->getScriptObject(); } CATCH_AND_THROW("Fail in exec!"); } Local DBSessionClass::close(const Arguments& args) { CHECK_ARGS_COUNT(args, 0); try { session->close(); return Boolean::newBoolean(true); } CATCH_WITHOUT_RETURN("Fail in close!"); return Boolean::newBoolean(false); } Local DBSessionClass::isOpen(const Arguments& args) { CHECK_ARGS_COUNT(args, 0); try { return Boolean::newBoolean(session->isOpen()); } CATCH_AND_THROW("Fail in isOpen!"); } //////////////////// Classes DBStmt //////////////////// // 生成函数 DBStmtClass::DBStmtClass(const Local& scriptObj, const DB::SharedPointer& stmt) : ScriptClass(scriptObj) , stmt(stmt) { } DBStmtClass::DBStmtClass(const DB::SharedPointer& stmt) : ScriptClass(script::ScriptClass::ConstructFromCpp{}) , stmt(stmt) { } DBStmtClass::~DBStmtClass() { } Local DBStmtClass::getAffectedRows() { try { auto res = stmt->getAffectedRows(); if (res == (uint64_t)-1) return Number::newNumber(-1); if (res > LLONG_MAX) return Number::newNumber((double)res); return Number::newNumber((int64_t)res); } CATCH_AND_THROW("Fail in getAffectedRows!"); } Local DBStmtClass::getInsertId() { try { auto res = stmt->getInsertId(); if (res == (uint64_t)-1) return Number::newNumber(-1); if (res > LLONG_MAX) return Number::newNumber((double)res); return Number::newNumber((int64_t)res); } CATCH_AND_THROW("Fail in getInsertId!"); } Local DBStmtClass::bind(const Arguments& args) { try { switch (args.size()) { case 1: { switch (args[0].getKind()) { case ValueKind::kArray: { auto arr = args[0].asArray(); for (size_t i = 0; i < arr.size(); ++i) stmt->bind(LocalValueToAny(arr.get(i))); break; } case ValueKind::kObject: { auto obj = args[0].asObject(); for (auto& key : obj.getKeys()) stmt->bind(LocalValueToAny(obj.get(key)), key.toString()); break; } default: stmt->bind(LocalValueToAny(args[0])); } break; } case 2: { if (args[1].isNumber()) { stmt->bind(LocalValueToAny(args[0]), (int)args[1].asNumber().toInt64()); } else if (args[1].isString()) { stmt->bind(LocalValueToAny(args[0]), args[1].asString().toString()); } else { LOG_WRONG_ARG_TYPE(); } } } return this->getScriptObject(); } CATCH_AND_THROW("Fail in bind!"); } Local DBStmtClass::execute(const Arguments& args) { CHECK_ARGS_COUNT(args, 0); try { stmt->execute(); return this->getScriptObject(); } CATCH_AND_THROW("Fail in reset!"); } Local DBStmtClass::step(const Arguments& args) { CHECK_ARGS_COUNT(args, 0); try { return Boolean::newBoolean(stmt->step()); } CATCH_WITHOUT_RETURN("Fail in step!"); return Boolean::newBoolean(false); } Local DBStmtClass::fetch(const Arguments& args) { CHECK_ARGS_COUNT(args, 0); try { return RowToLocalValue(stmt->fetch()); } CATCH_AND_THROW("Fail in fetch!"); } Local DBStmtClass::fetchAll(const Arguments& args) { try { switch (args.size()) { case 0: return RowSetToLocalValue(stmt->fetchAll()); case 1: { CHECK_ARG_TYPE(args[0], ValueKind::kFunction); auto func = args[0].asFunction(); stmt->fetchAll([&](const Row& row) { auto res = func.call({}, RowToLocalValue(row)); if (res.isBoolean()) { return res.asBoolean().value(); } return true; }); } } return this->getScriptObject(); } CATCH_AND_THROW("Fail in fetchAll!"); } Local DBStmtClass::reset(const Arguments& args) { CHECK_ARGS_COUNT(args, 0); try { stmt->reset(); return this->getScriptObject(); } CATCH_AND_THROW("Fail in reset!"); } Local DBStmtClass::reexec(const Arguments& args) { CHECK_ARGS_COUNT(args, 0); try { stmt->reexec(); return this->getScriptObject(); } CATCH_AND_THROW("Fail in reexec!"); } Local DBStmtClass::clear(const Arguments& args) { CHECK_ARGS_COUNT(args, 0); try { stmt->clear(); return this->getScriptObject(); } CATCH_AND_THROW("Fail in clear!"); }