diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml new file mode 100644 index 0000000..7528e38 --- /dev/null +++ b/.github/workflows/static_checks.yml @@ -0,0 +1,33 @@ +# NOTE: The content of this file has been directly adapted from /godot-cpp/.workflows/static_checks.yml +name: 📊 Static Checks +on: [push, pull_request] + +jobs: + static-checks: + name: Format (clang-format) + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: true + submodules: recursive + + # Azure repositories are not reliable, we need to prevent Azure giving us packages. + - name: Make apt sources.list use the default Ubuntu repositories + run: | + sudo rm -f /etc/apt/sources.list.d/* + sudo cp -f godot-cpp/misc/ci/sources.list /etc/apt/sources.list + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + sudo apt-add-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-15 main" + sudo apt-get update + + - name: Install dependencies + run: | + sudo apt-get install -qq clang-format-15 + sudo update-alternatives --remove-all clang-format || true + sudo update-alternatives --install /usr/bin/clang-format clang-format /usr/bin/clang-format-15 100 + + - name: Style checks via clang-format (clang_format.sh) + run: | + bash ./misc/scripts/clang_format.sh \ No newline at end of file diff --git a/misc/scripts/clang_format.sh b/misc/scripts/clang_format.sh new file mode 100644 index 0000000..f0be51a --- /dev/null +++ b/misc/scripts/clang_format.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +# This script runs clang-format on all relevant files in the repo. +# This is the primary script responsible for fixing style violations. + +set -uo pipefail + +# Loops through all code files tracked by Git. +git ls-files -- '*.c' '*.h' '*.cpp' '*.hpp' ':!:src/sqlite/*' | +while read -r f; do + # Run clang-format. + clang-format --style=file:godot-cpp/.clang-format --Wno-error=unknown -i "$f" +done + +diff=$(git diff --color) + +# If no patch has been generated all is OK, clean up, and exit. +if [ -z "$diff" ] ; then + printf "Files in this commit comply with the clang-tidy style rules.\n" + exit 0 +fi + +# A patch has been created, notify the user, clean up, and exit. +printf "\n*** The following changes have been made to comply with the formatting rules:\n\n" +echo "$diff" +printf "\n*** Please fix your commit(s) with 'git commit --amend' or 'git rebase -i '\n" +exit 1 diff --git a/src/gdsqlite.cpp b/src/gdsqlite.cpp index 813eb70..9422446 100644 --- a/src/gdsqlite.cpp +++ b/src/gdsqlite.cpp @@ -2,1228 +2,1069 @@ using namespace godot; -void SQLite::_bind_methods() -{ - // Methods. - ClassDB::bind_method(D_METHOD("open_db"), &SQLite::open_db); - ClassDB::bind_method(D_METHOD("close_db"), &SQLite::close_db); - ClassDB::bind_method(D_METHOD("query", "query_string"), &SQLite::query); - ClassDB::bind_method(D_METHOD("query_with_bindings", "query_string", "param_bindings"), &SQLite::query_with_bindings); +void SQLite::_bind_methods() { + // Methods. + ClassDB::bind_method(D_METHOD("open_db"), &SQLite::open_db); + ClassDB::bind_method(D_METHOD("close_db"), &SQLite::close_db); + ClassDB::bind_method(D_METHOD("query", "query_string"), &SQLite::query); + ClassDB::bind_method(D_METHOD("query_with_bindings", "query_string", "param_bindings"), &SQLite::query_with_bindings); - ClassDB::bind_method(D_METHOD("create_table", "table_name", "table_data"), &SQLite::create_table); - ClassDB::bind_method(D_METHOD("drop_table", "table_name"), &SQLite::drop_table); + ClassDB::bind_method(D_METHOD("create_table", "table_name", "table_data"), &SQLite::create_table); + ClassDB::bind_method(D_METHOD("drop_table", "table_name"), &SQLite::drop_table); - ClassDB::bind_method(D_METHOD("backup_to", "destination"), &SQLite::backup_to); - ClassDB::bind_method(D_METHOD("restore_from", "source"), &SQLite::restore_from); + ClassDB::bind_method(D_METHOD("backup_to", "destination"), &SQLite::backup_to); + ClassDB::bind_method(D_METHOD("restore_from", "source"), &SQLite::restore_from); - ClassDB::bind_method(D_METHOD("insert_row", "table_name", "row_data"), &SQLite::insert_row); - ClassDB::bind_method(D_METHOD("insert_rows", "table_name", "row_array"), &SQLite::insert_rows); + ClassDB::bind_method(D_METHOD("insert_row", "table_name", "row_data"), &SQLite::insert_row); + ClassDB::bind_method(D_METHOD("insert_rows", "table_name", "row_array"), &SQLite::insert_rows); - ClassDB::bind_method(D_METHOD("select_rows", "table_name", "conditions", "columns"), &SQLite::select_rows); - ClassDB::bind_method(D_METHOD("update_rows", "table_name", "conditions", "row_data"), &SQLite::update_rows); - ClassDB::bind_method(D_METHOD("delete_rows", "table_name", "conditions"), &SQLite::delete_rows); + ClassDB::bind_method(D_METHOD("select_rows", "table_name", "conditions", "columns"), &SQLite::select_rows); + ClassDB::bind_method(D_METHOD("update_rows", "table_name", "conditions", "row_data"), &SQLite::update_rows); + ClassDB::bind_method(D_METHOD("delete_rows", "table_name", "conditions"), &SQLite::delete_rows); - ClassDB::bind_method(D_METHOD("create_function", "function_name", "callable", "arguments"), &SQLite::create_function); + ClassDB::bind_method(D_METHOD("create_function", "function_name", "callable", "arguments"), &SQLite::create_function); - ClassDB::bind_method(D_METHOD("import_from_json", "import_path"), &SQLite::import_from_json); - ClassDB::bind_method(D_METHOD("export_to_json", "export_path"), &SQLite::export_to_json); + ClassDB::bind_method(D_METHOD("import_from_json", "import_path"), &SQLite::import_from_json); + ClassDB::bind_method(D_METHOD("export_to_json", "export_path"), &SQLite::export_to_json); - ClassDB::bind_method(D_METHOD("get_autocommit"), &SQLite::get_autocommit); + ClassDB::bind_method(D_METHOD("get_autocommit"), &SQLite::get_autocommit); - // Properties. - ClassDB::bind_method(D_METHOD("set_last_insert_rowid", "last_insert_rowid"), &SQLite::set_last_insert_rowid); - ClassDB::bind_method(D_METHOD("get_last_insert_rowid"), &SQLite::get_last_insert_rowid); - ADD_PROPERTY(PropertyInfo(Variant::INT, "last_insert_rowid"), "set_last_insert_rowid", "get_last_insert_rowid"); + // Properties. + ClassDB::bind_method(D_METHOD("set_last_insert_rowid", "last_insert_rowid"), &SQLite::set_last_insert_rowid); + ClassDB::bind_method(D_METHOD("get_last_insert_rowid"), &SQLite::get_last_insert_rowid); + ADD_PROPERTY(PropertyInfo(Variant::INT, "last_insert_rowid"), "set_last_insert_rowid", "get_last_insert_rowid"); - ClassDB::bind_method(D_METHOD("set_verbosity_level", "verbosity_level"), &SQLite::set_verbosity_level); - ClassDB::bind_method(D_METHOD("get_verbosity_level"), &SQLite::get_verbosity_level); - ADD_PROPERTY(PropertyInfo(Variant::INT, "verbosity_level"), "set_verbosity_level", "get_verbosity_level"); + ClassDB::bind_method(D_METHOD("set_verbosity_level", "verbosity_level"), &SQLite::set_verbosity_level); + ClassDB::bind_method(D_METHOD("get_verbosity_level"), &SQLite::get_verbosity_level); + ADD_PROPERTY(PropertyInfo(Variant::INT, "verbosity_level"), "set_verbosity_level", "get_verbosity_level"); - ClassDB::bind_method(D_METHOD("set_foreign_keys", "foreign_keys"), &SQLite::set_foreign_keys); - ClassDB::bind_method(D_METHOD("get_foreign_keys"), &SQLite::get_foreign_keys); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "foreign_keys"), "set_foreign_keys", "get_foreign_keys"); + ClassDB::bind_method(D_METHOD("set_foreign_keys", "foreign_keys"), &SQLite::set_foreign_keys); + ClassDB::bind_method(D_METHOD("get_foreign_keys"), &SQLite::get_foreign_keys); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "foreign_keys"), "set_foreign_keys", "get_foreign_keys"); - ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &SQLite::set_read_only); - ClassDB::bind_method(D_METHOD("get_read_only"), &SQLite::get_read_only); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "get_read_only"); + ClassDB::bind_method(D_METHOD("set_read_only", "read_only"), &SQLite::set_read_only); + ClassDB::bind_method(D_METHOD("get_read_only"), &SQLite::get_read_only); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "read_only"), "set_read_only", "get_read_only"); - ClassDB::bind_method(D_METHOD("set_path", "path"), &SQLite::set_path); - ClassDB::bind_method(D_METHOD("get_path"), &SQLite::get_path); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "path"), "set_path", "get_path"); + ClassDB::bind_method(D_METHOD("set_path", "path"), &SQLite::set_path); + ClassDB::bind_method(D_METHOD("get_path"), &SQLite::get_path); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "path"), "set_path", "get_path"); - ClassDB::bind_method(D_METHOD("set_error_message", "error_message"), &SQLite::set_error_message); - ClassDB::bind_method(D_METHOD("get_error_message"), &SQLite::get_error_message); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "error_message"), "set_error_message", "get_error_message"); + ClassDB::bind_method(D_METHOD("set_error_message", "error_message"), &SQLite::set_error_message); + ClassDB::bind_method(D_METHOD("get_error_message"), &SQLite::get_error_message); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "error_message"), "set_error_message", "get_error_message"); - ClassDB::bind_method(D_METHOD("set_default_extension", "default_extension"), &SQLite::set_default_extension); - ClassDB::bind_method(D_METHOD("get_default_extension"), &SQLite::get_default_extension); - ADD_PROPERTY(PropertyInfo(Variant::STRING, "default_extension"), "set_default_extension", "get_default_extension"); + ClassDB::bind_method(D_METHOD("set_default_extension", "default_extension"), &SQLite::set_default_extension); + ClassDB::bind_method(D_METHOD("get_default_extension"), &SQLite::get_default_extension); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "default_extension"), "set_default_extension", "get_default_extension"); - ClassDB::bind_method(D_METHOD("set_query_result", "query_result"), &SQLite::set_query_result); - ClassDB::bind_method(D_METHOD("get_query_result"), &SQLite::get_query_result); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "query_result", PROPERTY_HINT_ARRAY_TYPE, "Dictionary"), "set_query_result", "get_query_result"); + ClassDB::bind_method(D_METHOD("set_query_result", "query_result"), &SQLite::set_query_result); + ClassDB::bind_method(D_METHOD("get_query_result"), &SQLite::get_query_result); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "query_result", PROPERTY_HINT_ARRAY_TYPE, "Dictionary"), "set_query_result", "get_query_result"); - ClassDB::bind_method(D_METHOD("get_query_result_by_reference"), &SQLite::get_query_result_by_reference); - ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "query_result_by_reference", PROPERTY_HINT_ARRAY_TYPE, "Dictionary"), "set_query_result", "get_query_result_by_reference"); + ClassDB::bind_method(D_METHOD("get_query_result_by_reference"), &SQLite::get_query_result_by_reference); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "query_result_by_reference", PROPERTY_HINT_ARRAY_TYPE, "Dictionary"), "set_query_result", "get_query_result_by_reference"); - // Constants. - BIND_ENUM_CONSTANT(QUIET); - BIND_ENUM_CONSTANT(NORMAL); - BIND_ENUM_CONSTANT(VERBOSE); - BIND_ENUM_CONSTANT(VERY_VERBOSE); + // Constants. + BIND_ENUM_CONSTANT(QUIET); + BIND_ENUM_CONSTANT(NORMAL); + BIND_ENUM_CONSTANT(VERBOSE); + BIND_ENUM_CONSTANT(VERY_VERBOSE); } -SQLite::SQLite() -{ - db = nullptr; - query_result = TypedArray(); +SQLite::SQLite() { + db = nullptr; + query_result = TypedArray(); } -SQLite::~SQLite() -{ - /* Clean up the function_registry */ - function_registry.clear(); - function_registry.shrink_to_fit(); - /* Close the database connection if it is still open */ - if (db) { - close_db(); - } +SQLite::~SQLite() { + /* Clean up the function_registry */ + function_registry.clear(); + function_registry.shrink_to_fit(); + /* Close the database connection if it is still open */ + if (db) { + close_db(); + } } -bool SQLite::open_db() -{ - if (db) { - UtilityFunctions::printerr("GDSQLite Error: Can't open database if connection is already open!"); - return false; - } - - char *zErrMsg = 0; - int rc; - if (path.find(":memory:") == -1) - { - /* Add the default_extension to the database path if no extension is present */ - /* Skip if the default_extension is an empty string to allow for paths without extension */ - if (path.get_extension().is_empty() && !default_extension.is_empty()) - { - String ending = String(".") + default_extension; - path += ending; - } - - if (!read_only) - { - /* Find the real path */ - path = ProjectSettings::get_singleton()->globalize_path(path.strip_edges()); - } - } - - // TODO: Switch back to the `alloc_c_string()`-method once the API gets updated - const CharString dummy_path = path.utf8(); - const char *char_path = dummy_path.get_data(); - //const char *char_path = path.alloc_c_string(); - /* Try to open the database */ - if (read_only) - { - if (path.find(":memory:") == -1) - { - sqlite3_vfs_register(gdsqlite_vfs(), 0); - rc = sqlite3_open_v2(char_path, &db, SQLITE_OPEN_READONLY, "godot"); - } - else - { - UtilityFunctions::printerr("GDSQLite Error: Opening in-memory databases in read-only mode is currently not supported!"); - return false; - } - } - else - { - /* The `SQLITE_OPEN_URI`-flag is solely required for in-memory databases with shared cache, but it is safe to use in most general cases */ - /* As discussed here: https://www.sqlite.org/uri.html */ - rc = sqlite3_open_v2(char_path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, NULL); - /* Identical to: `rc = sqlite3_open(char_path, &db);`*/ - } - - if (rc != SQLITE_OK) - { - UtilityFunctions::printerr("GDSQLite Error: Can't open database: " + String::utf8(sqlite3_errmsg(db))); - return false; - } - else if (verbosity_level > VerbosityLevel::QUIET) - { - UtilityFunctions::print("Opened database successfully (" + path + ")"); - } - - /* Try to enable foreign keys. */ - if (foreign_keys) - { - rc = sqlite3_exec(db, "PRAGMA foreign_keys=on;", NULL, NULL, &zErrMsg); - if (rc != SQLITE_OK) - { - UtilityFunctions::printerr("GDSQLite Error: Can't enable foreign keys: " + String::utf8(zErrMsg)); - sqlite3_free(zErrMsg); - return false; - } - } - - return true; +bool SQLite::open_db() { + if (db) { + UtilityFunctions::printerr("GDSQLite Error: Can't open database if connection is already open!"); + return false; + } + + char *zErrMsg = 0; + int rc; + if (path.find(":memory:") == -1) { + /* Add the default_extension to the database path if no extension is present */ + /* Skip if the default_extension is an empty string to allow for paths without extension */ + if (path.get_extension().is_empty() && !default_extension.is_empty()) { + String ending = String(".") + default_extension; + path += ending; + } + + if (!read_only) { + /* Find the real path */ + path = ProjectSettings::get_singleton()->globalize_path(path.strip_edges()); + } + } + + // TODO: Switch back to the `alloc_c_string()`-method once the API gets updated + const CharString dummy_path = path.utf8(); + const char *char_path = dummy_path.get_data(); + //const char *char_path = path.alloc_c_string(); + /* Try to open the database */ + if (read_only) { + if (path.find(":memory:") == -1) { + sqlite3_vfs_register(gdsqlite_vfs(), 0); + rc = sqlite3_open_v2(char_path, &db, SQLITE_OPEN_READONLY, "godot"); + } else { + UtilityFunctions::printerr("GDSQLite Error: Opening in-memory databases in read-only mode is currently not supported!"); + return false; + } + } else { + /* The `SQLITE_OPEN_URI`-flag is solely required for in-memory databases with shared cache, but it is safe to use in most general cases */ + /* As discussed here: https://www.sqlite.org/uri.html */ + rc = sqlite3_open_v2(char_path, &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, NULL); + /* Identical to: `rc = sqlite3_open(char_path, &db);`*/ + } + + if (rc != SQLITE_OK) { + UtilityFunctions::printerr("GDSQLite Error: Can't open database: " + String::utf8(sqlite3_errmsg(db))); + return false; + } else if (verbosity_level > VerbosityLevel::QUIET) { + UtilityFunctions::print("Opened database successfully (" + path + ")"); + } + + /* Try to enable foreign keys. */ + if (foreign_keys) { + rc = sqlite3_exec(db, "PRAGMA foreign_keys=on;", NULL, NULL, &zErrMsg); + if (rc != SQLITE_OK) { + UtilityFunctions::printerr("GDSQLite Error: Can't enable foreign keys: " + String::utf8(zErrMsg)); + sqlite3_free(zErrMsg); + return false; + } + } + + return true; } -bool SQLite::close_db() -{ - if (db) - { - // Cannot close database! - if (sqlite3_close_v2(db) != SQLITE_OK) - { - UtilityFunctions::printerr("GDSQLite Error: Can't close database!"); - return false; - } - else - { - db = nullptr; - if (verbosity_level > VerbosityLevel::QUIET) - { - UtilityFunctions::print("Closed database (" + path + ")"); - } - return true; - } - } - - UtilityFunctions::printerr("GDSQLite Error: Can't close database if connection is not open!"); - return false; +bool SQLite::close_db() { + if (db) { + // Cannot close database! + if (sqlite3_close_v2(db) != SQLITE_OK) { + UtilityFunctions::printerr("GDSQLite Error: Can't close database!"); + return false; + } else { + db = nullptr; + if (verbosity_level > VerbosityLevel::QUIET) { + UtilityFunctions::print("Closed database (" + path + ")"); + } + return true; + } + } + + UtilityFunctions::printerr("GDSQLite Error: Can't close database if connection is not open!"); + return false; } -bool SQLite::query(const String &p_query) -{ - return query_with_bindings(p_query, Array()); +bool SQLite::query(const String &p_query) { + return query_with_bindings(p_query, Array()); } -bool SQLite::query_with_bindings(const String &p_query, Array param_bindings) -{ - const char *zErrMsg, *sql, *pzTail; - int rc; - - if (verbosity_level > VerbosityLevel::NORMAL) - { - UtilityFunctions::print(p_query); - } - // TODO: Switch back to the `alloc_c_string()`-method once the API gets updated - const CharString dummy_query = p_query.utf8(); - sql = dummy_query.get_data(); - //sql = p_query.alloc_c_string(); - - /* Clear the previous query results */ - query_result.clear(); - - sqlite3_stmt *stmt; - /* Prepare an SQL statement */ - rc = sqlite3_prepare_v2(db, sql, -1, &stmt, &pzTail); - zErrMsg = sqlite3_errmsg(db); - error_message = String::utf8(zErrMsg); - if (rc != SQLITE_OK) - { - UtilityFunctions::printerr(" --> SQL error: " + error_message); - sqlite3_finalize(stmt); - return false; - } - - /* Check if the param_bindings size exceeds the required parameter count */ - int parameter_count = sqlite3_bind_parameter_count(stmt); - if (param_bindings.size() < parameter_count) - { - UtilityFunctions::printerr("GDSQLite Error: Insufficient number of parameters to satisfy required number of bindings in statement!"); - sqlite3_finalize(stmt); - return false; - } - - /* Bind any given parameters to the prepared statement */ - for (int i = 0; i < parameter_count; i++) - { - Variant binding_value = param_bindings.pop_front(); - switch (binding_value.get_type()) - { - case Variant::NIL: - sqlite3_bind_null(stmt, i + 1); - break; - - case Variant::BOOL: - case Variant::INT: - sqlite3_bind_int64(stmt, i + 1, int64_t(binding_value)); - break; - - case Variant::FLOAT: - sqlite3_bind_double(stmt, i + 1, binding_value); - break; - - case Variant::STRING: - // TODO: Switch back to the `alloc_c_string()`-method once the API gets updated - { - const CharString dummy_binding = (binding_value.operator String()).utf8(); - const char *binding = dummy_binding.get_data(); - sqlite3_bind_text(stmt, i + 1, binding, -1, SQLITE_TRANSIENT); - } - //sqlite3_bind_text(stmt, i + 1, (binding_value.operator String()).alloc_c_string(), -1, SQLITE_TRANSIENT); - break; - - case Variant::PACKED_BYTE_ARRAY: - { - PackedByteArray binding = ((const PackedByteArray &)binding_value); - /* Calling .ptr() on an empty PackedByteArray returns an error */ - if (binding.size() == 0) - { - sqlite3_bind_null(stmt, i + 1); - /* Identical to: `sqlite3_bind_blob64(stmt, i + 1, nullptr, 0, SQLITE_TRANSIENT);`*/ - } - else - { - sqlite3_bind_blob64(stmt, i + 1, binding.ptr(), binding.size(), SQLITE_TRANSIENT); - } - break; - } - - default: - UtilityFunctions::printerr("GDSQLite Error: Binding a parameter of type " + String(std::to_string(binding_value.get_type()).c_str()) + " (TYPE_*) is not supported!"); - sqlite3_finalize(stmt); - return false; - } - } - - if (verbosity_level > VerbosityLevel::NORMAL) - { - char *expanded_sql = sqlite3_expanded_sql(stmt); - UtilityFunctions::print(String::utf8(expanded_sql)); - sqlite3_free(expanded_sql); - } - - // Execute the statement and iterate over all the resulting rows. - while (sqlite3_step(stmt) == SQLITE_ROW) - { - Dictionary column_dict; - int argc = sqlite3_column_count(stmt); - - /* Loop over all columns and add them to the Dictionary */ - for (int i = 0; i < argc; i++) - { - Variant column_value; - /* Check the column type and do correct casting */ - switch (sqlite3_column_type(stmt, i)) - { - case SQLITE_INTEGER: - column_value = Variant((int64_t)sqlite3_column_int64(stmt, i)); - break; - - case SQLITE_FLOAT: - column_value = Variant(sqlite3_column_double(stmt, i)); - break; - - case SQLITE_TEXT: - column_value = Variant(String::utf8((char *)sqlite3_column_text(stmt, i))); - break; - - case SQLITE_BLOB: - { - int bytes = sqlite3_column_bytes(stmt, i); - PackedByteArray arr = PackedByteArray(); - arr.resize(bytes); - memcpy((void *)arr.ptrw(), (char *)sqlite3_column_blob(stmt, i), bytes); - column_value = arr; - break; - } - - case SQLITE_NULL: - break; - - default: - break; - } - - const char *azColName = sqlite3_column_name(stmt, i); - column_dict[String::utf8(azColName)] = column_value; - } - /* Add result to query_result Array */ - query_result.append(column_dict); - } - - /* Clean up and delete the resources used by the prepared statement */ - sqlite3_finalize(stmt); - - rc = sqlite3_errcode(db); - zErrMsg = sqlite3_errmsg(db); - error_message = String::utf8(zErrMsg); - if (rc != SQLITE_OK) - { - UtilityFunctions::printerr(" --> SQL error: " + error_message); - return false; - } - else if (verbosity_level > VerbosityLevel::NORMAL) - { - UtilityFunctions::print(" --> Query succeeded"); - } - - /* Figure out if there's a subsequent statement which needs execution */ - String sTail = String(pzTail).strip_edges(); - if (!sTail.is_empty()) - { - return query_with_bindings(sTail, param_bindings); - } - - if (!param_bindings.is_empty()) - { - UtilityFunctions::push_warning("GDSQLite Warning: Provided number of bindings exceeded the required number in statement! (" + String(std::to_string(param_bindings.size()).c_str()) + " unused parameter(s))"); - } - - return true; +bool SQLite::query_with_bindings(const String &p_query, Array param_bindings) { + const char *zErrMsg, *sql, *pzTail; + int rc; + + if (verbosity_level > VerbosityLevel::NORMAL) { + UtilityFunctions::print(p_query); + } + // TODO: Switch back to the `alloc_c_string()`-method once the API gets updated + const CharString dummy_query = p_query.utf8(); + sql = dummy_query.get_data(); + //sql = p_query.alloc_c_string(); + + /* Clear the previous query results */ + query_result.clear(); + + sqlite3_stmt *stmt; + /* Prepare an SQL statement */ + rc = sqlite3_prepare_v2(db, sql, -1, &stmt, &pzTail); + zErrMsg = sqlite3_errmsg(db); + error_message = String::utf8(zErrMsg); + if (rc != SQLITE_OK) { + UtilityFunctions::printerr(" --> SQL error: " + error_message); + sqlite3_finalize(stmt); + return false; + } + + /* Check if the param_bindings size exceeds the required parameter count */ + int parameter_count = sqlite3_bind_parameter_count(stmt); + if (param_bindings.size() < parameter_count) { + UtilityFunctions::printerr("GDSQLite Error: Insufficient number of parameters to satisfy required number of bindings in statement!"); + sqlite3_finalize(stmt); + return false; + } + + /* Bind any given parameters to the prepared statement */ + for (int i = 0; i < parameter_count; i++) { + Variant binding_value = param_bindings.pop_front(); + switch (binding_value.get_type()) { + case Variant::NIL: + sqlite3_bind_null(stmt, i + 1); + break; + + case Variant::BOOL: + case Variant::INT: + sqlite3_bind_int64(stmt, i + 1, int64_t(binding_value)); + break; + + case Variant::FLOAT: + sqlite3_bind_double(stmt, i + 1, binding_value); + break; + + case Variant::STRING: + // TODO: Switch back to the `alloc_c_string()`-method once the API gets updated + { + const CharString dummy_binding = (binding_value.operator String()).utf8(); + const char *binding = dummy_binding.get_data(); + sqlite3_bind_text(stmt, i + 1, binding, -1, SQLITE_TRANSIENT); + } + //sqlite3_bind_text(stmt, i + 1, (binding_value.operator String()).alloc_c_string(), -1, SQLITE_TRANSIENT); + break; + + case Variant::PACKED_BYTE_ARRAY: { + PackedByteArray binding = ((const PackedByteArray &)binding_value); + /* Calling .ptr() on an empty PackedByteArray returns an error */ + if (binding.size() == 0) { + sqlite3_bind_null(stmt, i + 1); + /* Identical to: `sqlite3_bind_blob64(stmt, i + 1, nullptr, 0, SQLITE_TRANSIENT);`*/ + } else { + sqlite3_bind_blob64(stmt, i + 1, binding.ptr(), binding.size(), SQLITE_TRANSIENT); + } + break; + } + + default: + UtilityFunctions::printerr("GDSQLite Error: Binding a parameter of type " + String(std::to_string(binding_value.get_type()).c_str()) + " (TYPE_*) is not supported!"); + sqlite3_finalize(stmt); + return false; + } + } + + if (verbosity_level > VerbosityLevel::NORMAL) { + char *expanded_sql = sqlite3_expanded_sql(stmt); + UtilityFunctions::print(String::utf8(expanded_sql)); + sqlite3_free(expanded_sql); + } + + // Execute the statement and iterate over all the resulting rows. + while (sqlite3_step(stmt) == SQLITE_ROW) { + Dictionary column_dict; + int argc = sqlite3_column_count(stmt); + + /* Loop over all columns and add them to the Dictionary */ + for (int i = 0; i < argc; i++) { + Variant column_value; + /* Check the column type and do correct casting */ + switch (sqlite3_column_type(stmt, i)) { + case SQLITE_INTEGER: + column_value = Variant((int64_t)sqlite3_column_int64(stmt, i)); + break; + + case SQLITE_FLOAT: + column_value = Variant(sqlite3_column_double(stmt, i)); + break; + + case SQLITE_TEXT: + column_value = Variant(String::utf8((char *)sqlite3_column_text(stmt, i))); + break; + + case SQLITE_BLOB: { + int bytes = sqlite3_column_bytes(stmt, i); + PackedByteArray arr = PackedByteArray(); + arr.resize(bytes); + memcpy((void *)arr.ptrw(), (char *)sqlite3_column_blob(stmt, i), bytes); + column_value = arr; + break; + } + + case SQLITE_NULL: + break; + + default: + break; + } + + const char *azColName = sqlite3_column_name(stmt, i); + column_dict[String::utf8(azColName)] = column_value; + } + /* Add result to query_result Array */ + query_result.append(column_dict); + } + + /* Clean up and delete the resources used by the prepared statement */ + sqlite3_finalize(stmt); + + rc = sqlite3_errcode(db); + zErrMsg = sqlite3_errmsg(db); + error_message = String::utf8(zErrMsg); + if (rc != SQLITE_OK) { + UtilityFunctions::printerr(" --> SQL error: " + error_message); + return false; + } else if (verbosity_level > VerbosityLevel::NORMAL) { + UtilityFunctions::print(" --> Query succeeded"); + } + + /* Figure out if there's a subsequent statement which needs execution */ + String sTail = String(pzTail).strip_edges(); + if (!sTail.is_empty()) { + return query_with_bindings(sTail, param_bindings); + } + + if (!param_bindings.is_empty()) { + UtilityFunctions::push_warning("GDSQLite Warning: Provided number of bindings exceeded the required number in statement! (" + String(std::to_string(param_bindings.size()).c_str()) + " unused parameter(s))"); + } + + return true; } -bool SQLite::create_table(const String &p_name, const Dictionary &p_table_dict) -{ - if (!validate_table_dict(p_table_dict)) { - return false; - } - - String query_string, type_string, key_string; - String integer_datatype = "int"; - /* Create SQL statement */ - query_string = "CREATE TABLE IF NOT EXISTS " + p_name + " ("; - key_string = ""; - - Dictionary column_dict; - Array columns = p_table_dict.keys(); - int64_t number_of_columns = columns.size(); - for (int64_t i = 0; i <= number_of_columns - 1; i++) - { - column_dict = p_table_dict[columns[i]]; - query_string += (const String &)columns[i] + String(" "); - type_string = (const String &)column_dict["data_type"]; - if (type_string.to_lower().begins_with(integer_datatype)) - { - query_string += String("INTEGER"); - } - else - { - query_string += type_string; - } - - /* Primary key check */ - if (column_dict.get("primary_key", false)) - { - query_string += String(" PRIMARY KEY"); - /* Autoincrement check */ - if (column_dict.get("auto_increment", false)) - { - query_string += String(" AUTOINCREMENT"); - } - } - /* Not null check */ - if (column_dict.get("not_null", false)) - { - query_string += String(" NOT NULL"); - } - /* Unique check */ - if (column_dict.get("unique", false)) - { - query_string += String(" UNIQUE"); - } - /* Default check */ - if (column_dict.has("default")) - { - query_string += String(" DEFAULT ") + (const String &)column_dict["default"]; - } - /* Apply foreign key constraint. */ - if (foreign_keys) - { - if (column_dict.get("foreign_key", false)) - { - const String foreign_key_definition = (const String &)(column_dict["foreign_key"]); - const Array foreign_key_elements = foreign_key_definition.split("."); - if (foreign_key_elements.size() == 2) - { - const String column_name = (const String &)(columns[i]); - const String foreign_key_table_name = (const String &)(foreign_key_elements[0]); - const String foreign_key_column_name = (const String &)(foreign_key_elements[1]); - key_string += String(", FOREIGN KEY (" + column_name + ") REFERENCES " + foreign_key_table_name + "(" + foreign_key_column_name + ")"); - } - } - } - - if (i != number_of_columns - 1) - { - query_string += ","; - } - } - - query_string += key_string + ");"; - - return query(query_string); +bool SQLite::create_table(const String &p_name, const Dictionary &p_table_dict) { + if (!validate_table_dict(p_table_dict)) { + return false; + } + + String query_string, type_string, key_string; + String integer_datatype = "int"; + /* Create SQL statement */ + query_string = "CREATE TABLE IF NOT EXISTS " + p_name + " ("; + key_string = ""; + + Dictionary column_dict; + Array columns = p_table_dict.keys(); + int64_t number_of_columns = columns.size(); + for (int64_t i = 0; i <= number_of_columns - 1; i++) { + column_dict = p_table_dict[columns[i]]; + query_string += (const String &)columns[i] + String(" "); + type_string = (const String &)column_dict["data_type"]; + if (type_string.to_lower().begins_with(integer_datatype)) { + query_string += String("INTEGER"); + } else { + query_string += type_string; + } + + /* Primary key check */ + if (column_dict.get("primary_key", false)) { + query_string += String(" PRIMARY KEY"); + /* Autoincrement check */ + if (column_dict.get("auto_increment", false)) { + query_string += String(" AUTOINCREMENT"); + } + } + /* Not null check */ + if (column_dict.get("not_null", false)) { + query_string += String(" NOT NULL"); + } + /* Unique check */ + if (column_dict.get("unique", false)) { + query_string += String(" UNIQUE"); + } + /* Default check */ + if (column_dict.has("default")) { + query_string += String(" DEFAULT ") + (const String &)column_dict["default"]; + } + /* Apply foreign key constraint. */ + if (foreign_keys) { + if (column_dict.get("foreign_key", false)) { + const String foreign_key_definition = (const String &)(column_dict["foreign_key"]); + const Array foreign_key_elements = foreign_key_definition.split("."); + if (foreign_key_elements.size() == 2) { + const String column_name = (const String &)(columns[i]); + const String foreign_key_table_name = (const String &)(foreign_key_elements[0]); + const String foreign_key_column_name = (const String &)(foreign_key_elements[1]); + key_string += String(", FOREIGN KEY (" + column_name + ") REFERENCES " + foreign_key_table_name + "(" + foreign_key_column_name + ")"); + } + } + } + + if (i != number_of_columns - 1) { + query_string += ","; + } + } + + query_string += key_string + ");"; + + return query(query_string); } -bool SQLite::validate_table_dict(const Dictionary &p_table_dict) -{ - Dictionary column_dict; - Array columns = p_table_dict.keys(); - int64_t number_of_columns = columns.size(); - for (int64_t i = 0; i <= number_of_columns - 1; i++) - { - if (p_table_dict[columns[i]].get_type() != Variant::DICTIONARY) - { - UtilityFunctions::printerr("GDSQLite Error: All values of the table dictionary should be of type Dictionary"); - return false; - } - - column_dict = p_table_dict[columns[i]]; - if (!column_dict.has("data_type")) - { - UtilityFunctions::printerr("GDSQLite Error: The field \"data_type\" is a required part of the table dictionary"); - return false; - } - - if (column_dict["data_type"].get_type() != Variant::STRING) - { - UtilityFunctions::printerr("GDSQLite Error: The field \"data_type\" should be of type String"); - return false; - } - - if (column_dict.has("default")) - { - Variant::Type default_type = column_dict["default"].get_type(); - - CharString dummy_data_type = ((const String &)column_dict["data_type"]).utf8(); - const char *char_data_type = dummy_data_type.get_data(); - - /* Get the type of the "datatype"-field and compare with the type of the "default"-value */ - /* Some types are not checked and might be added in a future version */ - Variant::Type data_type_type = default_type; - if (strcmp(char_data_type, "int") == 0) { - data_type_type = Variant::Type::INT; - } - else if (strcmp(char_data_type, "text") == 0) { - data_type_type = Variant::Type::STRING; - } - else if (strcmp(char_data_type, "real") == 0) { - data_type_type = Variant::Type::FLOAT; - } - - if (data_type_type != default_type) { - UtilityFunctions::printerr("GDSQLite Error: The type of the field \"default\" ( " + String(std::to_string(default_type).c_str()) + " ) should be the same type as the \"datatype\"-field ( " + String(std::to_string(data_type_type).c_str()) + " )"); - return false; - } - } - } - - return true; +bool SQLite::validate_table_dict(const Dictionary &p_table_dict) { + Dictionary column_dict; + Array columns = p_table_dict.keys(); + int64_t number_of_columns = columns.size(); + for (int64_t i = 0; i <= number_of_columns - 1; i++) { + if (p_table_dict[columns[i]].get_type() != Variant::DICTIONARY) { + UtilityFunctions::printerr("GDSQLite Error: All values of the table dictionary should be of type Dictionary"); + return false; + } + + column_dict = p_table_dict[columns[i]]; + if (!column_dict.has("data_type")) { + UtilityFunctions::printerr("GDSQLite Error: The field \"data_type\" is a required part of the table dictionary"); + return false; + } + + if (column_dict["data_type"].get_type() != Variant::STRING) { + UtilityFunctions::printerr("GDSQLite Error: The field \"data_type\" should be of type String"); + return false; + } + + if (column_dict.has("default")) { + Variant::Type default_type = column_dict["default"].get_type(); + + CharString dummy_data_type = ((const String &)column_dict["data_type"]).utf8(); + const char *char_data_type = dummy_data_type.get_data(); + + /* Get the type of the "datatype"-field and compare with the type of the "default"-value */ + /* Some types are not checked and might be added in a future version */ + Variant::Type data_type_type = default_type; + if (strcmp(char_data_type, "int") == 0) { + data_type_type = Variant::Type::INT; + } else if (strcmp(char_data_type, "text") == 0) { + data_type_type = Variant::Type::STRING; + } else if (strcmp(char_data_type, "real") == 0) { + data_type_type = Variant::Type::FLOAT; + } + + if (data_type_type != default_type) { + UtilityFunctions::printerr("GDSQLite Error: The type of the field \"default\" ( " + String(std::to_string(default_type).c_str()) + " ) should be the same type as the \"datatype\"-field ( " + String(std::to_string(data_type_type).c_str()) + " )"); + return false; + } + } + } + + return true; } -bool SQLite::drop_table(const String &p_name) -{ - String query_string; - /* Create SQL statement */ - query_string = "DROP TABLE " + p_name + ";"; +bool SQLite::drop_table(const String &p_name) { + String query_string; + /* Create SQL statement */ + query_string = "DROP TABLE " + p_name + ";"; - return query(query_string); + return query(query_string); } bool SQLite::backup_to(String destination_path) { - destination_path = ProjectSettings::get_singleton()->globalize_path(destination_path.strip_edges()); - CharString dummy_path = destination_path.utf8(); - const char *char_path = dummy_path.get_data(); - - sqlite3 *destination_db; - int result = sqlite3_open_v2(char_path, &destination_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, NULL); - if (result == SQLITE_OK) { - result = backup_database(db, destination_db); - } - (void)sqlite3_close_v2(destination_db); - return result == SQLITE_OK; + destination_path = ProjectSettings::get_singleton()->globalize_path(destination_path.strip_edges()); + CharString dummy_path = destination_path.utf8(); + const char *char_path = dummy_path.get_data(); + + sqlite3 *destination_db; + int result = sqlite3_open_v2(char_path, &destination_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, NULL); + if (result == SQLITE_OK) { + result = backup_database(db, destination_db); + } + (void)sqlite3_close_v2(destination_db); + return result == SQLITE_OK; } bool SQLite::restore_from(String source_path) { - source_path = ProjectSettings::get_singleton()->globalize_path(source_path.strip_edges()); - CharString dummy_path = source_path.utf8(); - const char *char_path = dummy_path.get_data(); - - sqlite3 *source_db; - int result = sqlite3_open_v2(char_path, &source_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, NULL); - if (result == SQLITE_OK) { - result = backup_database(source_db, db); - } - (void)sqlite3_close_v2(source_db); - return result == SQLITE_OK; + source_path = ProjectSettings::get_singleton()->globalize_path(source_path.strip_edges()); + CharString dummy_path = source_path.utf8(); + const char *char_path = dummy_path.get_data(); + + sqlite3 *source_db; + int result = sqlite3_open_v2(char_path, &source_db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI, NULL); + if (result == SQLITE_OK) { + result = backup_database(source_db, db); + } + (void)sqlite3_close_v2(source_db); + return result == SQLITE_OK; } int SQLite::backup_database(sqlite3 *source_db, sqlite3 *destination_db) { - int rc; - sqlite3_backup *backup = sqlite3_backup_init(destination_db, "main", source_db, "main"); - if (backup) { - (void)sqlite3_backup_step(backup, -1); - (void)sqlite3_backup_finish(backup); - } - - rc = sqlite3_errcode(destination_db); - return rc; + int rc; + sqlite3_backup *backup = sqlite3_backup_init(destination_db, "main", source_db, "main"); + if (backup) { + (void)sqlite3_backup_step(backup, -1); + (void)sqlite3_backup_finish(backup); + } + + rc = sqlite3_errcode(destination_db); + return rc; } -bool SQLite::insert_row(const String &p_name, const Dictionary &p_row_dict) -{ - String query_string, key_string, value_string = ""; - Array keys = p_row_dict.keys(); - Array param_bindings = p_row_dict.values(); - - /* Create SQL statement */ - query_string = "INSERT INTO " + p_name; - - int64_t number_of_keys = p_row_dict.size(); - for (int64_t i = 0; i <= number_of_keys - 1; i++) - { - key_string += (const String &)keys[i]; - value_string += "?"; - if (i != number_of_keys - 1) - { - key_string += ","; - value_string += ","; - } - } - query_string += " (" + key_string + ") VALUES (" + value_string + ");"; - - return query_with_bindings(query_string, param_bindings); +bool SQLite::insert_row(const String &p_name, const Dictionary &p_row_dict) { + String query_string, key_string, value_string = ""; + Array keys = p_row_dict.keys(); + Array param_bindings = p_row_dict.values(); + + /* Create SQL statement */ + query_string = "INSERT INTO " + p_name; + + int64_t number_of_keys = p_row_dict.size(); + for (int64_t i = 0; i <= number_of_keys - 1; i++) { + key_string += (const String &)keys[i]; + value_string += "?"; + if (i != number_of_keys - 1) { + key_string += ","; + value_string += ","; + } + } + query_string += " (" + key_string + ") VALUES (" + value_string + ");"; + + return query_with_bindings(query_string, param_bindings); } -bool SQLite::insert_rows(const String &p_name, const Array &p_row_array) -{ - query("BEGIN TRANSACTION;"); - int64_t number_of_rows = p_row_array.size(); - for (int64_t i = 0; i <= number_of_rows - 1; i++) - { - if (p_row_array[i].get_type() != Variant::DICTIONARY) - { - UtilityFunctions::printerr("GDSQLite Error: All elements of the Array should be of type Dictionary"); - /* Don't forget to close the transaction! */ - /* Maybe we should do a rollback instead? */ - query("END TRANSACTION;"); - return false; - } - if (!insert_row(p_name, p_row_array[i])) - { - /* Don't forget to close the transaction! */ - /* Stop the error_message from being overwritten! */ - String previous_error_message = error_message; - query("END TRANSACTION;"); - error_message = previous_error_message; - return false; - } - } - query("END TRANSACTION;"); - return true; +bool SQLite::insert_rows(const String &p_name, const Array &p_row_array) { + query("BEGIN TRANSACTION;"); + int64_t number_of_rows = p_row_array.size(); + for (int64_t i = 0; i <= number_of_rows - 1; i++) { + if (p_row_array[i].get_type() != Variant::DICTIONARY) { + UtilityFunctions::printerr("GDSQLite Error: All elements of the Array should be of type Dictionary"); + /* Don't forget to close the transaction! */ + /* Maybe we should do a rollback instead? */ + query("END TRANSACTION;"); + return false; + } + if (!insert_row(p_name, p_row_array[i])) { + /* Don't forget to close the transaction! */ + /* Stop the error_message from being overwritten! */ + String previous_error_message = error_message; + query("END TRANSACTION;"); + error_message = previous_error_message; + return false; + } + } + query("END TRANSACTION;"); + return true; } -Array SQLite::select_rows(const String &p_name, const String &p_conditions, const Array &p_columns_array) -{ - String query_string; - /* Create SQL statement */ - query_string = "SELECT "; - - int64_t number_of_columns = p_columns_array.size(); - for (int64_t i = 0; i <= number_of_columns - 1; i++) - { - if (p_columns_array[i].get_type() != Variant::STRING) - { - UtilityFunctions::printerr("GDSQLite Error: All elements of the Array should be of type String"); - return query_result; - } - query_string += (const String &)p_columns_array[i]; - - if (i != number_of_columns - 1) - { - query_string += ", "; - } - } - query_string += " FROM " + p_name; - if (!p_conditions.is_empty()) - { - query_string += " WHERE " + p_conditions; - } - query_string += ";"; - - query(query_string); - /* Return the duplicated result */ - return get_query_result(); +Array SQLite::select_rows(const String &p_name, const String &p_conditions, const Array &p_columns_array) { + String query_string; + /* Create SQL statement */ + query_string = "SELECT "; + + int64_t number_of_columns = p_columns_array.size(); + for (int64_t i = 0; i <= number_of_columns - 1; i++) { + if (p_columns_array[i].get_type() != Variant::STRING) { + UtilityFunctions::printerr("GDSQLite Error: All elements of the Array should be of type String"); + return query_result; + } + query_string += (const String &)p_columns_array[i]; + + if (i != number_of_columns - 1) { + query_string += ", "; + } + } + query_string += " FROM " + p_name; + if (!p_conditions.is_empty()) { + query_string += " WHERE " + p_conditions; + } + query_string += ";"; + + query(query_string); + /* Return the duplicated result */ + return get_query_result(); } -bool SQLite::update_rows(const String &p_name, const String &p_conditions, const Dictionary &p_updated_row_dict) -{ - String query_string; - Array param_bindings; - bool success; - - int64_t number_of_keys = p_updated_row_dict.size(); - Array keys = p_updated_row_dict.keys(); - Array values = p_updated_row_dict.values(); - - query("BEGIN TRANSACTION;"); - /* Create SQL statement */ - query_string += "UPDATE " + p_name + " SET "; - - for (int64_t i = 0; i <= number_of_keys - 1; i++) - { - query_string += (const String &)keys[i] + String("=?"); - param_bindings.append(values[i]); - if (i != number_of_keys - 1) - { - query_string += ", "; - } - } - query_string += " WHERE " + p_conditions + ";"; - - success = query_with_bindings(query_string, param_bindings); - /* Stop the error_message from being overwritten! */ - String previous_error_message = error_message; - query("END TRANSACTION;"); - error_message = previous_error_message; - return success; +bool SQLite::update_rows(const String &p_name, const String &p_conditions, const Dictionary &p_updated_row_dict) { + String query_string; + Array param_bindings; + bool success; + + int64_t number_of_keys = p_updated_row_dict.size(); + Array keys = p_updated_row_dict.keys(); + Array values = p_updated_row_dict.values(); + + query("BEGIN TRANSACTION;"); + /* Create SQL statement */ + query_string += "UPDATE " + p_name + " SET "; + + for (int64_t i = 0; i <= number_of_keys - 1; i++) { + query_string += (const String &)keys[i] + String("=?"); + param_bindings.append(values[i]); + if (i != number_of_keys - 1) { + query_string += ", "; + } + } + query_string += " WHERE " + p_conditions + ";"; + + success = query_with_bindings(query_string, param_bindings); + /* Stop the error_message from being overwritten! */ + String previous_error_message = error_message; + query("END TRANSACTION;"); + error_message = previous_error_message; + return success; } -bool SQLite::delete_rows(const String &p_name, const String &p_conditions) -{ - String query_string; - bool success; - - query("BEGIN TRANSACTION;"); - /* Create SQL statement */ - query_string = "DELETE FROM " + p_name; - /* If it's empty or * everything is to be deleted */ - if (!p_conditions.is_empty() && (p_conditions != (const String &)"*")) - { - query_string += " WHERE " + p_conditions; - } - query_string += ";"; - - success = query(query_string); - /* Stop the error_message from being overwritten! */ - String previous_error_message = error_message; - query("END TRANSACTION;"); - error_message = previous_error_message; - return success; +bool SQLite::delete_rows(const String &p_name, const String &p_conditions) { + String query_string; + bool success; + + query("BEGIN TRANSACTION;"); + /* Create SQL statement */ + query_string = "DELETE FROM " + p_name; + /* If it's empty or * everything is to be deleted */ + if (!p_conditions.is_empty() && (p_conditions != (const String &)"*")) { + query_string += " WHERE " + p_conditions; + } + query_string += ";"; + + success = query(query_string); + /* Stop the error_message from being overwritten! */ + String previous_error_message = error_message; + query("END TRANSACTION;"); + error_message = previous_error_message; + return success; } -static void function_callback(sqlite3_context *context, int argc, sqlite3_value **argv) -{ - void *temp = sqlite3_user_data(context); - Callable callable = *(Callable *)temp; - - /* Check if the callable is valid */ - if (!callable.is_valid()) - { - UtilityFunctions::printerr("GDSQLite Error: Supplied function reference is invalid! Aborting callback..."); - return; - } - - Array argument_array = Array(); - Variant argument_value; - for (int i = 0; i <= argc - 1; i++) - { - sqlite3_value *value = *argv; - /* Check the value type and do correct casting */ - switch (sqlite3_value_type(value)) - { - case SQLITE_INTEGER: - argument_value = Variant((int64_t)sqlite3_value_int64(value)); - break; - - case SQLITE_FLOAT: - argument_value = Variant(sqlite3_value_double(value)); - break; - - case SQLITE_TEXT: - argument_value = Variant((char *)sqlite3_value_text(value)); - break; - - case SQLITE_BLOB: - { - int bytes = sqlite3_value_bytes(value); - PackedByteArray arr = PackedByteArray(); - arr.resize(bytes); - memcpy((void *)arr.ptrw(), (char *)sqlite3_value_blob(value), bytes); - argument_value = arr; - break; - } - - case SQLITE_NULL: - break; - - default: - break; - } - - argument_array.append(argument_value); - argv += 1; - } - - Variant output = callable.callv(argument_array); - - switch (output.get_type()) - { - case Variant::NIL: - sqlite3_result_null(context); - break; - - case Variant::BOOL: - case Variant::INT: - sqlite3_result_int64(context, int64_t(output)); - break; - - case Variant::FLOAT: - sqlite3_result_double(context, output); - break; - - case Variant::STRING: - // TODO: Switch back to the `alloc_c_string()`-method once the API gets updated - { - const CharString dummy_binding = (output.operator String()).utf8(); - const char *binding = dummy_binding.get_data(); - sqlite3_result_text(context, binding, -1, SQLITE_STATIC); - } - //sqlite3_result_text(context, (output.operator String()).alloc_c_string(), -1, SQLITE_STATIC); - break; - - case Variant::PACKED_BYTE_ARRAY: - { - PackedByteArray arr = ((const PackedByteArray &)output); - sqlite3_result_blob(context, arr.ptr(), arr.size(), SQLITE_TRANSIENT); - break; - } - - default: - break; - } +static void function_callback(sqlite3_context *context, int argc, sqlite3_value **argv) { + void *temp = sqlite3_user_data(context); + Callable callable = *(Callable *)temp; + + /* Check if the callable is valid */ + if (!callable.is_valid()) { + UtilityFunctions::printerr("GDSQLite Error: Supplied function reference is invalid! Aborting callback..."); + return; + } + + Array argument_array = Array(); + Variant argument_value; + for (int i = 0; i <= argc - 1; i++) { + sqlite3_value *value = *argv; + /* Check the value type and do correct casting */ + switch (sqlite3_value_type(value)) { + case SQLITE_INTEGER: + argument_value = Variant((int64_t)sqlite3_value_int64(value)); + break; + + case SQLITE_FLOAT: + argument_value = Variant(sqlite3_value_double(value)); + break; + + case SQLITE_TEXT: + argument_value = Variant((char *)sqlite3_value_text(value)); + break; + + case SQLITE_BLOB: { + int bytes = sqlite3_value_bytes(value); + PackedByteArray arr = PackedByteArray(); + arr.resize(bytes); + memcpy((void *)arr.ptrw(), (char *)sqlite3_value_blob(value), bytes); + argument_value = arr; + break; + } + + case SQLITE_NULL: + break; + + default: + break; + } + + argument_array.append(argument_value); + argv += 1; + } + + Variant output = callable.callv(argument_array); + + switch (output.get_type()) { + case Variant::NIL: + sqlite3_result_null(context); + break; + + case Variant::BOOL: + case Variant::INT: + sqlite3_result_int64(context, int64_t(output)); + break; + + case Variant::FLOAT: + sqlite3_result_double(context, output); + break; + + case Variant::STRING: + // TODO: Switch back to the `alloc_c_string()`-method once the API gets updated + { + const CharString dummy_binding = (output.operator String()).utf8(); + const char *binding = dummy_binding.get_data(); + sqlite3_result_text(context, binding, -1, SQLITE_STATIC); + } + //sqlite3_result_text(context, (output.operator String()).alloc_c_string(), -1, SQLITE_STATIC); + break; + + case Variant::PACKED_BYTE_ARRAY: { + PackedByteArray arr = ((const PackedByteArray &)output); + sqlite3_result_blob(context, arr.ptr(), arr.size(), SQLITE_TRANSIENT); + break; + } + + default: + break; + } } -bool SQLite::create_function(const String &p_name, const Callable &p_callable, int p_argc) -{ - /* The exact memory position of the std::vector's elements changes during memory reallocation (= when adding additional elements) */ - /* Luckily, the pointer to the managed object (of the std::unique_ptr) won't change during execution! (= consistent) */ - /* The std::unique_ptr is stored in a std::vector and is thus allocated on the heap */ - function_registry.push_back(std::make_unique(p_callable)); - - int rc; - CharString dummy_name = p_name.utf8(); - const char *zFunctionName = dummy_name.get_data(); - //const char *zFunctionName = p_name.alloc_c_string(); - int nArg = p_argc; - int eTextRep = SQLITE_UTF8; - - /* Get a void pointer to the managed object of the smart pointer that is stored at the back of the vector */ - void *pApp = (void *)function_registry.back().get(); - void (*xFunc)(sqlite3_context *, int, sqlite3_value **) = function_callback; - void (*xStep)(sqlite3_context *, int, sqlite3_value **) = NULL; - void (*xFinal)(sqlite3_context *) = NULL; - - /* Create the actual function */ - rc = sqlite3_create_function(db, zFunctionName, nArg, eTextRep, pApp, xFunc, xStep, xFinal); - if (rc) - { - UtilityFunctions::printerr("GDSQLite Error: " + String(sqlite3_errmsg(db))); - return false; - } - else if (verbosity_level > VerbosityLevel::NORMAL) - { - UtilityFunctions::print("Succesfully added function \"" + p_name + "\" to function registry"); - } - return true; +bool SQLite::create_function(const String &p_name, const Callable &p_callable, int p_argc) { + /* The exact memory position of the std::vector's elements changes during memory reallocation (= when adding additional elements) */ + /* Luckily, the pointer to the managed object (of the std::unique_ptr) won't change during execution! (= consistent) */ + /* The std::unique_ptr is stored in a std::vector and is thus allocated on the heap */ + function_registry.push_back(std::make_unique(p_callable)); + + int rc; + CharString dummy_name = p_name.utf8(); + const char *zFunctionName = dummy_name.get_data(); + //const char *zFunctionName = p_name.alloc_c_string(); + int nArg = p_argc; + int eTextRep = SQLITE_UTF8; + + /* Get a void pointer to the managed object of the smart pointer that is stored at the back of the vector */ + void *pApp = (void *)function_registry.back().get(); + void (*xFunc)(sqlite3_context *, int, sqlite3_value **) = function_callback; + void (*xStep)(sqlite3_context *, int, sqlite3_value **) = NULL; + void (*xFinal)(sqlite3_context *) = NULL; + + /* Create the actual function */ + rc = sqlite3_create_function(db, zFunctionName, nArg, eTextRep, pApp, xFunc, xStep, xFinal); + if (rc) { + UtilityFunctions::printerr("GDSQLite Error: " + String(sqlite3_errmsg(db))); + return false; + } else if (verbosity_level > VerbosityLevel::NORMAL) { + UtilityFunctions::print("Succesfully added function \"" + p_name + "\" to function registry"); + } + return true; } -bool SQLite::import_from_json(String import_path) -{ - /* Add .json to the import_path String if not present */ - String ending = String(".json"); - if (!import_path.ends_with(ending)) - { - import_path += ending; - } - /* Find the real path */ - import_path = ProjectSettings::get_singleton()->globalize_path(import_path.strip_edges()); - CharString dummy_path = import_path.utf8(); - const char *char_path = dummy_path.get_data(); - //const char *char_path = import_path.alloc_c_string(); - - /* Open the json-file and stream its content into a stringstream */ - std::ifstream ifs(char_path); - if (ifs.fail()) - { - UtilityFunctions::printerr("GDSQLite Error: Failed to open specified json-file (" + import_path + ")"); - return false; - } - std::stringstream buffer; - buffer << ifs.rdbuf(); - std::string str = buffer.str(); - String json_string = String::utf8(str.c_str()); - ifs.close(); - - /* Attempt to parse the result and, if unsuccessful, throw a parse error specifying the erroneous line */ - Ref json; - json.instantiate(); - Error error = json->parse(json_string); - if (error != Error::OK) - { - /* Throw a parsing error */ - // TODO: Figure out how to cast a int32_t to a Godot String using the new API - UtilityFunctions::printerr("GDSQLite Error: parsing failed! reason: " + json->get_error_message() + ", at line: ???"); - //GODOT_LOG(2, "GDSQLite Error: parsing failed! reason: " + result->get_error_string() + ", at line: " + String::num_int64(result->get_error_line())) - return false; - } - Array database_array = json->get_data(); - std::vector objects_to_import; - /* Validate the json structure and populate the tables_to_import vector */ - if (!validate_json(database_array, objects_to_import)) - { - return false; - } - - /* Check if the database is open and, if not, attempt to open it */ - if (db == nullptr) - { - /* Open the database using the open_db method */ - if (!open_db()) - { - return false; - } - } - - /* Find all tables that are present in this database */ - /* We don't care about triggers here since they get dropped automatically when their table is dropped */ - query(String("SELECT name FROM sqlite_master WHERE type = 'table';")); - TypedArray old_database_array = query_result.duplicate(true); - int64_t old_number_of_tables = query_result.size(); - /* Drop all old tables present in the database */ - for (int64_t i = 0; i <= old_number_of_tables - 1; i++) - { - Dictionary table_dict = old_database_array[i]; - String table_name = table_dict["name"]; - - drop_table(table_name); - } - - query("BEGIN TRANSACTION;"); - /* foreign_keys cannot be enforced until after all rows have been added! */ - query("PRAGMA defer_foreign_keys=on;"); - /* Add all new tables and fill them up with all the rows */ - for (auto table : objects_to_import) - { - if (!query(table.sql)) - { - /* Don't forget to close the transaction! */ - /* Stop the error_message from being overwritten! */ - String previous_error_message = error_message; - query("END TRANSACTION;"); - error_message = previous_error_message; - } - } - - for (auto object : objects_to_import) - { - if (object.type != TABLE) - { - /* The object is a trigger and doesn't have any rows! */ - continue; - } - - /* Convert the base64-encoded columns back to raw data */ - for (int64_t i = 0; i <= object.base64_columns.size() - 1; i++) - { - String key = object.base64_columns[i]; - for (int64_t j = 0; j <= object.row_array.size() - 1; j++) - { - Dictionary row = object.row_array[j]; - - if (row.has(key)) - { - String encoded_string = ((const String &)row[key]); - PackedByteArray arr = Marshalls::get_singleton()->base64_to_raw(encoded_string); - row[key] = arr; - } - } - } - - int64_t number_of_rows = object.row_array.size(); - for (int64_t i = 0; i <= number_of_rows - 1; i++) - { - if (object.row_array[i].get_type() != Variant::DICTIONARY) - { - UtilityFunctions::printerr("GDSQLite Error: All elements of the Array should be of type Dictionary"); - return false; - } - if (!insert_row(object.name, object.row_array[i])) - { - /* Don't forget to close the transaction! */ - /* Stop the error_message from being overwritten! */ - String previous_error_message = error_message; - query("END TRANSACTION;"); - error_message = previous_error_message; - return false; - } - } - } - query("END TRANSACTION;"); - return true; +bool SQLite::import_from_json(String import_path) { + /* Add .json to the import_path String if not present */ + String ending = String(".json"); + if (!import_path.ends_with(ending)) { + import_path += ending; + } + /* Find the real path */ + import_path = ProjectSettings::get_singleton()->globalize_path(import_path.strip_edges()); + CharString dummy_path = import_path.utf8(); + const char *char_path = dummy_path.get_data(); + //const char *char_path = import_path.alloc_c_string(); + + /* Open the json-file and stream its content into a stringstream */ + std::ifstream ifs(char_path); + if (ifs.fail()) { + UtilityFunctions::printerr("GDSQLite Error: Failed to open specified json-file (" + import_path + ")"); + return false; + } + std::stringstream buffer; + buffer << ifs.rdbuf(); + std::string str = buffer.str(); + String json_string = String::utf8(str.c_str()); + ifs.close(); + + /* Attempt to parse the result and, if unsuccessful, throw a parse error specifying the erroneous line */ + Ref json; + json.instantiate(); + Error error = json->parse(json_string); + if (error != Error::OK) { + /* Throw a parsing error */ + // TODO: Figure out how to cast a int32_t to a Godot String using the new API + UtilityFunctions::printerr("GDSQLite Error: parsing failed! reason: " + json->get_error_message() + ", at line: ???"); + //GODOT_LOG(2, "GDSQLite Error: parsing failed! reason: " + result->get_error_string() + ", at line: " + String::num_int64(result->get_error_line())) + return false; + } + Array database_array = json->get_data(); + std::vector objects_to_import; + /* Validate the json structure and populate the tables_to_import vector */ + if (!validate_json(database_array, objects_to_import)) { + return false; + } + + /* Check if the database is open and, if not, attempt to open it */ + if (db == nullptr) { + /* Open the database using the open_db method */ + if (!open_db()) { + return false; + } + } + + /* Find all tables that are present in this database */ + /* We don't care about triggers here since they get dropped automatically when their table is dropped */ + query(String("SELECT name FROM sqlite_master WHERE type = 'table';")); + TypedArray old_database_array = query_result.duplicate(true); + int64_t old_number_of_tables = query_result.size(); + /* Drop all old tables present in the database */ + for (int64_t i = 0; i <= old_number_of_tables - 1; i++) { + Dictionary table_dict = old_database_array[i]; + String table_name = table_dict["name"]; + + drop_table(table_name); + } + + query("BEGIN TRANSACTION;"); + /* foreign_keys cannot be enforced until after all rows have been added! */ + query("PRAGMA defer_foreign_keys=on;"); + /* Add all new tables and fill them up with all the rows */ + for (auto table : objects_to_import) { + if (!query(table.sql)) { + /* Don't forget to close the transaction! */ + /* Stop the error_message from being overwritten! */ + String previous_error_message = error_message; + query("END TRANSACTION;"); + error_message = previous_error_message; + } + } + + for (auto object : objects_to_import) { + if (object.type != TABLE) { + /* The object is a trigger and doesn't have any rows! */ + continue; + } + + /* Convert the base64-encoded columns back to raw data */ + for (int64_t i = 0; i <= object.base64_columns.size() - 1; i++) { + String key = object.base64_columns[i]; + for (int64_t j = 0; j <= object.row_array.size() - 1; j++) { + Dictionary row = object.row_array[j]; + + if (row.has(key)) { + String encoded_string = ((const String &)row[key]); + PackedByteArray arr = Marshalls::get_singleton()->base64_to_raw(encoded_string); + row[key] = arr; + } + } + } + + int64_t number_of_rows = object.row_array.size(); + for (int64_t i = 0; i <= number_of_rows - 1; i++) { + if (object.row_array[i].get_type() != Variant::DICTIONARY) { + UtilityFunctions::printerr("GDSQLite Error: All elements of the Array should be of type Dictionary"); + return false; + } + if (!insert_row(object.name, object.row_array[i])) { + /* Don't forget to close the transaction! */ + /* Stop the error_message from being overwritten! */ + String previous_error_message = error_message; + query("END TRANSACTION;"); + error_message = previous_error_message; + return false; + } + } + } + query("END TRANSACTION;"); + return true; } -bool SQLite::export_to_json(String export_path) -{ - /* Get all names and sql templates for all tables present in the database */ - query(String("SELECT name,sql,type FROM sqlite_master WHERE type = 'table' OR type = 'trigger';")); - int64_t number_of_objects = query_result.size(); - TypedArray database_array = query_result.duplicate(true); - /* Construct a Dictionary for each table, convert it to JSON and write it to file */ - for (int64_t i = 0; i <= number_of_objects - 1; i++) - { - Dictionary object_dict = database_array[i]; - - if (object_dict["type"] == String("table")) - { - String object_name = object_dict["name"]; - String query_string; - - query_string = "SELECT * FROM " + (const String &)object_name + ";"; - query(query_string); - - /* Encode all columns of type PoolByteArray to base64 */ - if (!query_result.is_empty()) - { - /* First identify the columns that are of this type! */ - Array base64_columns = Array(); - Dictionary initial_row = query_result[0]; - Array keys = initial_row.keys(); - for (int k = 0; k <= keys.size() - 1; k++) - { - String key = keys[k]; - Variant value = initial_row[key]; - if (value.get_type() == Variant::PACKED_BYTE_ARRAY) - { - base64_columns.append(key); - } - } - - /* Now go through all the rows and encode the relevant columns */ - for (int k = 0; k <= base64_columns.size() - 1; k++) - { - String key = base64_columns[k]; - for (int j = 0; j <= query_result.size() - 1; j++) - { - Dictionary row = query_result[j]; - PackedByteArray arr = ((const PackedByteArray &)row[key]); - String encoded_string = Marshalls::get_singleton()->raw_to_base64(arr); - - row.erase(key); - row[key] = encoded_string; - } - } - - if (!base64_columns.is_empty()) - { - object_dict["base64_columns"] = base64_columns; - } - } - object_dict["row_array"] = query_result.duplicate(true); - } - } - - /* Add .json to the import_path String if not present */ - String ending = String(".json"); - if (!export_path.ends_with(ending)) - { - export_path += ending; - } - /* Find the real path */ - export_path = ProjectSettings::get_singleton()->globalize_path(export_path.strip_edges()); - CharString dummy_path = export_path.utf8(); - const char *char_path = dummy_path.get_data(); - //const char *char_path = export_path.alloc_c_string(); - - std::ofstream ofs(char_path, std::ios::trunc); - if (ofs.fail()) - { - UtilityFunctions::printerr("GDSQLite Error: Can't open specified json-file, file does not exist or is locked"); - return false; - } - Ref json; - json.instantiate(); - String json_string = json->stringify(database_array, "\t"); - CharString dummy_string = json_string.utf8(); - ofs << dummy_string.get_data(); - //ofs << json_string.alloc_c_string(); - ofs.close(); - - return true; +bool SQLite::export_to_json(String export_path) { + /* Get all names and sql templates for all tables present in the database */ + query(String("SELECT name,sql,type FROM sqlite_master WHERE type = 'table' OR type = 'trigger';")); + int64_t number_of_objects = query_result.size(); + TypedArray database_array = query_result.duplicate(true); + /* Construct a Dictionary for each table, convert it to JSON and write it to file */ + for (int64_t i = 0; i <= number_of_objects - 1; i++) { + Dictionary object_dict = database_array[i]; + + if (object_dict["type"] == String("table")) { + String object_name = object_dict["name"]; + String query_string; + + query_string = "SELECT * FROM " + (const String &)object_name + ";"; + query(query_string); + + /* Encode all columns of type PoolByteArray to base64 */ + if (!query_result.is_empty()) { + /* First identify the columns that are of this type! */ + Array base64_columns = Array(); + Dictionary initial_row = query_result[0]; + Array keys = initial_row.keys(); + for (int k = 0; k <= keys.size() - 1; k++) { + String key = keys[k]; + Variant value = initial_row[key]; + if (value.get_type() == Variant::PACKED_BYTE_ARRAY) { + base64_columns.append(key); + } + } + + /* Now go through all the rows and encode the relevant columns */ + for (int k = 0; k <= base64_columns.size() - 1; k++) { + String key = base64_columns[k]; + for (int j = 0; j <= query_result.size() - 1; j++) { + Dictionary row = query_result[j]; + PackedByteArray arr = ((const PackedByteArray &)row[key]); + String encoded_string = Marshalls::get_singleton()->raw_to_base64(arr); + + row.erase(key); + row[key] = encoded_string; + } + } + + if (!base64_columns.is_empty()) { + object_dict["base64_columns"] = base64_columns; + } + } + object_dict["row_array"] = query_result.duplicate(true); + } + } + + /* Add .json to the import_path String if not present */ + String ending = String(".json"); + if (!export_path.ends_with(ending)) { + export_path += ending; + } + /* Find the real path */ + export_path = ProjectSettings::get_singleton()->globalize_path(export_path.strip_edges()); + CharString dummy_path = export_path.utf8(); + const char *char_path = dummy_path.get_data(); + //const char *char_path = export_path.alloc_c_string(); + + std::ofstream ofs(char_path, std::ios::trunc); + if (ofs.fail()) { + UtilityFunctions::printerr("GDSQLite Error: Can't open specified json-file, file does not exist or is locked"); + return false; + } + Ref json; + json.instantiate(); + String json_string = json->stringify(database_array, "\t"); + CharString dummy_string = json_string.utf8(); + ofs << dummy_string.get_data(); + //ofs << json_string.alloc_c_string(); + ofs.close(); + + return true; } -bool SQLite::validate_json(const Array &database_array, std::vector &objects_to_import) -{ - /* Start going through all the tables and checking their validity */ - int64_t number_of_objects = database_array.size(); - for (int64_t i = 0; i <= number_of_objects - 1; i++) - { - /* Create a new object_struct */ - object_struct new_object; - - Dictionary temp_dict = database_array[i]; - /* Get the name of the object */ - if (!temp_dict.has("name")) - { - /* Did not find the necessary key! */ - UtilityFunctions::printerr("GDSQlite Error: Did not find required key \"name\" in the supplied json-file"); - return false; - } - new_object.name = temp_dict["name"]; - - /* Extract the sql template for generating the object */ - if (!temp_dict.has("sql")) - { - /* Did not find the necessary key! */ - UtilityFunctions::printerr("GDSQlite Error: Did not find required key \"sql\" in the supplied json-file"); - return false; - } - new_object.sql = temp_dict["sql"]; - - if (!temp_dict.has("type")) - { - /* Did not find the necessary key! */ - UtilityFunctions::printerr("GDSQlite Error: Did not find required key \"type\" in the supplied json-file"); - return false; - } - if (temp_dict["type"] == String("table")) - { - new_object.type = TABLE; - - /* Add the optional base64_columns if defined! */ - new_object.base64_columns = temp_dict.get("base64_columns", Array()); - - if (!temp_dict.has("row_array")) - { - /* Did not find the necessary key! */ - UtilityFunctions::printerr("GDSQlite Error: Did not find required key \"row_array\" in the supplied json-file"); - return false; - } - else if (Variant(temp_dict["row_array"]).get_type() != Variant::ARRAY) - { - UtilityFunctions::printerr("GDSQlite Error: The value of the key \"row_array\" should consist of an array of rows"); - return false; - } - new_object.row_array = temp_dict["row_array"]; - } - else if (temp_dict["type"] == String("trigger")) - { - new_object.type = TRIGGER; - } - else - { - /* Did not find the necessary key! */ - UtilityFunctions::printerr(2, "GDSQlite Error: The value of key \"type\" is restricted to either \"table\" or \"trigger\""); - return false; - } - - /* Add the table or trigger to the new objects vector */ - objects_to_import.insert(objects_to_import.end(), new_object); - } - return true; +bool SQLite::validate_json(const Array &database_array, std::vector &objects_to_import) { + /* Start going through all the tables and checking their validity */ + int64_t number_of_objects = database_array.size(); + for (int64_t i = 0; i <= number_of_objects - 1; i++) { + /* Create a new object_struct */ + object_struct new_object; + + Dictionary temp_dict = database_array[i]; + /* Get the name of the object */ + if (!temp_dict.has("name")) { + /* Did not find the necessary key! */ + UtilityFunctions::printerr("GDSQlite Error: Did not find required key \"name\" in the supplied json-file"); + return false; + } + new_object.name = temp_dict["name"]; + + /* Extract the sql template for generating the object */ + if (!temp_dict.has("sql")) { + /* Did not find the necessary key! */ + UtilityFunctions::printerr("GDSQlite Error: Did not find required key \"sql\" in the supplied json-file"); + return false; + } + new_object.sql = temp_dict["sql"]; + + if (!temp_dict.has("type")) { + /* Did not find the necessary key! */ + UtilityFunctions::printerr("GDSQlite Error: Did not find required key \"type\" in the supplied json-file"); + return false; + } + if (temp_dict["type"] == String("table")) { + new_object.type = TABLE; + + /* Add the optional base64_columns if defined! */ + new_object.base64_columns = temp_dict.get("base64_columns", Array()); + + if (!temp_dict.has("row_array")) { + /* Did not find the necessary key! */ + UtilityFunctions::printerr("GDSQlite Error: Did not find required key \"row_array\" in the supplied json-file"); + return false; + } else if (Variant(temp_dict["row_array"]).get_type() != Variant::ARRAY) { + UtilityFunctions::printerr("GDSQlite Error: The value of the key \"row_array\" should consist of an array of rows"); + return false; + } + new_object.row_array = temp_dict["row_array"]; + } else if (temp_dict["type"] == String("trigger")) { + new_object.type = TRIGGER; + } else { + /* Did not find the necessary key! */ + UtilityFunctions::printerr(2, "GDSQlite Error: The value of key \"type\" is restricted to either \"table\" or \"trigger\""); + return false; + } + + /* Add the table or trigger to the new objects vector */ + objects_to_import.insert(objects_to_import.end(), new_object); + } + return true; } // Properties. -void SQLite::set_last_insert_rowid(const int64_t &p_last_insert_rowid) -{ - if (db) - { - sqlite3_set_last_insert_rowid(db, p_last_insert_rowid); - } +void SQLite::set_last_insert_rowid(const int64_t &p_last_insert_rowid) { + if (db) { + sqlite3_set_last_insert_rowid(db, p_last_insert_rowid); + } } -int64_t SQLite::get_last_insert_rowid() const -{ - if (db) - { - return sqlite3_last_insert_rowid(db); - } - /* Return the default value */ - return 0; +int64_t SQLite::get_last_insert_rowid() const { + if (db) { + return sqlite3_last_insert_rowid(db); + } + /* Return the default value */ + return 0; } -void SQLite::set_verbosity_level(const int64_t &p_verbosity_level) -{ - verbosity_level = p_verbosity_level; +void SQLite::set_verbosity_level(const int64_t &p_verbosity_level) { + verbosity_level = p_verbosity_level; } -int64_t SQLite::get_verbosity_level() const -{ - return verbosity_level; +int64_t SQLite::get_verbosity_level() const { + return verbosity_level; } -void SQLite::set_foreign_keys(const bool &p_foreign_keys) -{ - foreign_keys = p_foreign_keys; +void SQLite::set_foreign_keys(const bool &p_foreign_keys) { + foreign_keys = p_foreign_keys; } -bool SQLite::get_foreign_keys() const -{ - return foreign_keys; +bool SQLite::get_foreign_keys() const { + return foreign_keys; } -void SQLite::set_read_only(const bool &p_read_only) -{ - read_only = p_read_only; +void SQLite::set_read_only(const bool &p_read_only) { + read_only = p_read_only; } -bool SQLite::get_read_only() const -{ - return read_only; +bool SQLite::get_read_only() const { + return read_only; } -void SQLite::set_path(const String &p_path) -{ - path = p_path; +void SQLite::set_path(const String &p_path) { + path = p_path; } -String SQLite::get_path() const -{ - return path; +String SQLite::get_path() const { + return path; } -void SQLite::set_error_message(const String &p_error_message) -{ - error_message = p_error_message; +void SQLite::set_error_message(const String &p_error_message) { + error_message = p_error_message; } -String SQLite::get_error_message() const -{ - return error_message; +String SQLite::get_error_message() const { + return error_message; } -void SQLite::set_default_extension(const String &p_default_extension) -{ - default_extension = p_default_extension; +void SQLite::set_default_extension(const String &p_default_extension) { + default_extension = p_default_extension; } -String SQLite::get_default_extension() const -{ - return default_extension; +String SQLite::get_default_extension() const { + return default_extension; } -void SQLite::set_query_result(const TypedArray &p_query_result) -{ - query_result = p_query_result; +void SQLite::set_query_result(const TypedArray &p_query_result) { + query_result = p_query_result; } -TypedArray SQLite::get_query_result() const -{ - return query_result.duplicate(true); +TypedArray SQLite::get_query_result() const { + return query_result.duplicate(true); } -TypedArray SQLite::get_query_result_by_reference() const -{ - return query_result; +TypedArray SQLite::get_query_result_by_reference() const { + return query_result; } -int SQLite::get_autocommit() const -{ - if (db) - { - return sqlite3_get_autocommit(db); - } - /* Return the default value */ - return 1; // A non-zero value indicates the autocommit is on! +int SQLite::get_autocommit() const { + if (db) { + return sqlite3_get_autocommit(db); + } + /* Return the default value */ + return 1; // A non-zero value indicates the autocommit is on! } diff --git a/src/gdsqlite.h b/src/gdsqlite.h index 3efce39..773ac98 100644 --- a/src/gdsqlite.h +++ b/src/gdsqlite.h @@ -7,123 +7,118 @@ #include #include -#include -#include #include +#include +#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include + +namespace godot { +enum OBJECT_TYPE { + TABLE, + TRIGGER +}; +struct object_struct { + String name, sql; + OBJECT_TYPE type; + Array base64_columns, row_array; +}; -namespace godot -{ - enum OBJECT_TYPE - { - TABLE, - TRIGGER - }; - struct object_struct - { - String name, sql; - OBJECT_TYPE type; - Array base64_columns, row_array; - }; +class SQLite : public RefCounted { + GDCLASS(SQLite, RefCounted) - class SQLite : public RefCounted - { - GDCLASS(SQLite, RefCounted) +private: + bool validate_json(const Array &import_json, std::vector &tables_to_import); + bool validate_table_dict(const Dictionary &p_table_dict); + int backup_database(sqlite3 *source_db, sqlite3 *destination_db); - private: - bool validate_json(const Array &import_json, std::vector &tables_to_import); - bool validate_table_dict(const Dictionary &p_table_dict); - int backup_database(sqlite3 *source_db, sqlite3 *destination_db); + sqlite3 *db; + std::vector> function_registry; - sqlite3 *db; - std::vector> function_registry; + int64_t verbosity_level = 1; + bool foreign_keys = false; + bool read_only = false; + String path = "default"; + String error_message = ""; + String default_extension = "db"; + TypedArray query_result = TypedArray(); - int64_t verbosity_level = 1; - bool foreign_keys = false; - bool read_only = false; - String path = "default"; - String error_message = ""; - String default_extension = "db"; - TypedArray query_result = TypedArray(); +protected: + static void _bind_methods(); - protected: - static void _bind_methods(); +public: + // Constants. + enum VerbosityLevel { + QUIET = 0, + NORMAL = 1, + VERBOSE = 2, + VERY_VERBOSE = 3 + }; - public: - // Constants. - enum VerbosityLevel - { - QUIET = 0, - NORMAL = 1, - VERBOSE = 2, - VERY_VERBOSE = 3 - }; + SQLite(); + ~SQLite(); - SQLite(); - ~SQLite(); + // Functions. + bool open_db(); + bool close_db(); + bool query(const String &p_query); + bool query_with_bindings(const String &p_query, Array param_bindings); - // Functions. - bool open_db(); - bool close_db(); - bool query(const String &p_query); - bool query_with_bindings(const String &p_query, Array param_bindings); + bool create_table(const String &p_name, const Dictionary &p_table_dict); + bool drop_table(const String &p_name); - bool create_table(const String &p_name, const Dictionary &p_table_dict); - bool drop_table(const String &p_name); + bool backup_to(String destination_path); + bool restore_from(String source_path); - bool backup_to(String destination_path); - bool restore_from(String source_path); + bool insert_row(const String &p_name, const Dictionary &p_row_dict); + bool insert_rows(const String &p_name, const Array &p_row_array); - bool insert_row(const String &p_name, const Dictionary &p_row_dict); - bool insert_rows(const String &p_name, const Array &p_row_array); + Array select_rows(const String &p_name, const String &p_conditions, const Array &p_columns_array); + bool update_rows(const String &p_name, const String &p_conditions, const Dictionary &p_updated_row_dict); + bool delete_rows(const String &p_name, const String &p_conditions); - Array select_rows(const String &p_name, const String &p_conditions, const Array &p_columns_array); - bool update_rows(const String &p_name, const String &p_conditions, const Dictionary &p_updated_row_dict); - bool delete_rows(const String &p_name, const String &p_conditions); + bool create_function(const String &p_name, const Callable &p_callable, int p_argc); - bool create_function(const String &p_name, const Callable &p_callable, int p_argc); + bool import_from_json(String import_path); + bool export_to_json(String export_path); - bool import_from_json(String import_path); - bool export_to_json(String export_path); + int get_autocommit() const; - int get_autocommit() const; + // Properties. + void set_last_insert_rowid(const int64_t &p_last_insert_rowid); + int64_t get_last_insert_rowid() const; - // Properties. - void set_last_insert_rowid(const int64_t &p_last_insert_rowid); - int64_t get_last_insert_rowid() const; + void set_verbosity_level(const int64_t &p_verbosity_level); + int64_t get_verbosity_level() const; - void set_verbosity_level(const int64_t &p_verbosity_level); - int64_t get_verbosity_level() const; + void set_foreign_keys(const bool &p_foreign_keys); + bool get_foreign_keys() const; - void set_foreign_keys(const bool &p_foreign_keys); - bool get_foreign_keys() const; + void set_read_only(const bool &p_read_only); + bool get_read_only() const; - void set_read_only(const bool &p_read_only); - bool get_read_only() const; + void set_path(const String &p_path); + String get_path() const; - void set_path(const String &p_path); - String get_path() const; + void set_error_message(const String &p_error_message); + String get_error_message() const; - void set_error_message(const String &p_error_message); - String get_error_message() const; + void set_default_extension(const String &p_default_extension); + String get_default_extension() const; - void set_default_extension(const String &p_default_extension); - String get_default_extension() const; + void set_query_result(const TypedArray &p_query_result); + TypedArray get_query_result() const; - void set_query_result(const TypedArray &p_query_result); - TypedArray get_query_result() const; - - TypedArray get_query_result_by_reference() const; - }; + TypedArray get_query_result_by_reference() const; +}; -} +} //namespace godot VARIANT_ENUM_CAST(SQLite::VerbosityLevel); diff --git a/src/register_types.cpp b/src/register_types.cpp index abd1422..9601f4b 100644 --- a/src/register_types.cpp +++ b/src/register_types.cpp @@ -11,7 +11,7 @@ using namespace godot; void initialize_sqlite_module(ModuleInitializationLevel p_level) { - if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } @@ -19,7 +19,7 @@ void initialize_sqlite_module(ModuleInitializationLevel p_level) { } void uninitialize_sqlite_module(ModuleInitializationLevel p_level) { - if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } } @@ -29,12 +29,12 @@ extern "C" { // Initialization. GDExtensionBool GDE_EXPORT sqlite_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, const GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { - godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); + godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); - init_obj.register_initializer(initialize_sqlite_module); - init_obj.register_terminator(uninitialize_sqlite_module); - init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); + init_obj.register_initializer(initialize_sqlite_module); + init_obj.register_terminator(uninitialize_sqlite_module); + init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); - return init_obj.init(); + return init_obj.init(); } } diff --git a/src/vfs/gdsqlite_file.cpp b/src/vfs/gdsqlite_file.cpp index 77a9707..1f03eee 100644 --- a/src/vfs/gdsqlite_file.cpp +++ b/src/vfs/gdsqlite_file.cpp @@ -5,97 +5,88 @@ using namespace godot; /* ** Close a file. */ -int gdsqlite_file::close(sqlite3_file *pFile) -{ - gdsqlite_file *p = reinterpret_cast(pFile); - ERR_FAIL_COND_V(!p->file->is_open(), SQLITE_IOERR_CLOSE); +int gdsqlite_file::close(sqlite3_file *pFile) { + gdsqlite_file *p = reinterpret_cast(pFile); + ERR_FAIL_COND_V(!p->file->is_open(), SQLITE_IOERR_CLOSE); - p->file->close(); - p->file.unref(); + p->file->close(); + p->file.unref(); - return SQLITE_OK; + return SQLITE_OK; } /* ** Read data from a file. */ -int gdsqlite_file::read(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst) -{ - gdsqlite_file *p = reinterpret_cast(pFile); - ERR_FAIL_COND_V(!p->file->is_open(), SQLITE_IOERR_CLOSE); - - /* Seek the wanted position in the file */ - p->file->seek(iOfst); - ERR_FAIL_COND_V(p->file->get_position() != iOfst, SQLITE_IOERR_READ); - - /* Read and populate the data */ - PackedByteArray arr = p->file->get_buffer(iAmt); - memcpy(zBuf, arr.ptr(), iAmt); - - if (arr.size() == iAmt) - { - return SQLITE_OK; - } - else if (arr.size() >= 0) - { - return SQLITE_IOERR_SHORT_READ; - } - - ERR_FAIL_V(SQLITE_IOERR_READ); +int gdsqlite_file::read(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst) { + gdsqlite_file *p = reinterpret_cast(pFile); + ERR_FAIL_COND_V(!p->file->is_open(), SQLITE_IOERR_CLOSE); + + /* Seek the wanted position in the file */ + p->file->seek(iOfst); + ERR_FAIL_COND_V(p->file->get_position() != iOfst, SQLITE_IOERR_READ); + + /* Read and populate the data */ + PackedByteArray arr = p->file->get_buffer(iAmt); + memcpy(zBuf, arr.ptr(), iAmt); + + if (arr.size() == iAmt) { + return SQLITE_OK; + } else if (arr.size() >= 0) { + return SQLITE_IOERR_SHORT_READ; + } + + ERR_FAIL_V(SQLITE_IOERR_READ); } /* ** Write data to a file. */ -int gdsqlite_file::write(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst) -{ - gdsqlite_file *p = reinterpret_cast(pFile); - ERR_FAIL_COND_V(!p->file->is_open(), SQLITE_IOERR_CLOSE); - - /* Seek the wanted position in the file */ - p->file->seek(iOfst); - ERR_FAIL_COND_V(p->file->get_position() != iOfst, SQLITE_IOERR_READ); - - /* Write the data to the file */ - PackedByteArray arr = PackedByteArray(); - arr.resize(iAmt); - memcpy(arr.ptrw(), zBuf, iAmt); - p->file->store_buffer(arr); - - /* Was the write succesful? */ - size_t bytes_written = p->file->get_position() - iOfst; - ERR_FAIL_COND_V(bytes_written != iAmt, SQLITE_IOERR_WRITE); - - return SQLITE_OK; +int gdsqlite_file::write(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst) { + gdsqlite_file *p = reinterpret_cast(pFile); + ERR_FAIL_COND_V(!p->file->is_open(), SQLITE_IOERR_CLOSE); + + /* Seek the wanted position in the file */ + p->file->seek(iOfst); + ERR_FAIL_COND_V(p->file->get_position() != iOfst, SQLITE_IOERR_READ); + + /* Write the data to the file */ + PackedByteArray arr = PackedByteArray(); + arr.resize(iAmt); + memcpy(arr.ptrw(), zBuf, iAmt); + p->file->store_buffer(arr); + + /* Was the write succesful? */ + size_t bytes_written = p->file->get_position() - iOfst; + ERR_FAIL_COND_V(bytes_written != iAmt, SQLITE_IOERR_WRITE); + + return SQLITE_OK; } /* ** Truncate a file. This is a no-op for this VFS. */ -int gdsqlite_file::truncate(sqlite3_file *pFile, sqlite_int64 size) -{ - return SQLITE_OK; +int gdsqlite_file::truncate(sqlite3_file *pFile, sqlite_int64 size) { + return SQLITE_OK; } /* ** Sync the contents of the file to the persistent media. */ -int gdsqlite_file::sync(sqlite3_file *pFile, int flags) -{ - return SQLITE_OK; +int gdsqlite_file::sync(sqlite3_file *pFile, int flags) { + return SQLITE_OK; } /* ** Write the size of the file in bytes to *pSize. */ -int gdsqlite_file::fileSize(sqlite3_file *pFile, sqlite_int64 *pSize) -{ - gdsqlite_file *p = reinterpret_cast(pFile); - ERR_FAIL_COND_V(!p->file->is_open(), SQLITE_IOERR_CLOSE); +int gdsqlite_file::fileSize(sqlite3_file *pFile, sqlite_int64 *pSize) { + gdsqlite_file *p = reinterpret_cast(pFile); + ERR_FAIL_COND_V(!p->file->is_open(), SQLITE_IOERR_CLOSE); - *pSize = p->file->get_length(); + *pSize = p->file->get_length(); - return SQLITE_OK; + return SQLITE_OK; } /* @@ -104,38 +95,32 @@ int gdsqlite_file::fileSize(sqlite3_file *pFile, sqlite_int64 *pSize) ** a reserved lock on the database file. This ensures that if a hot-journal ** file is found in the file-system it is rolled back. */ -int gdsqlite_file::lock(sqlite3_file *pFile, int eLock) -{ - return SQLITE_OK; +int gdsqlite_file::lock(sqlite3_file *pFile, int eLock) { + return SQLITE_OK; } -int gdsqlite_file::unlock(sqlite3_file *pFile, int eLock) -{ - return SQLITE_OK; +int gdsqlite_file::unlock(sqlite3_file *pFile, int eLock) { + return SQLITE_OK; } -int gdsqlite_file::checkReservedLock(sqlite3_file *pFile, int *pResOut) -{ - *pResOut = 0; - return SQLITE_OK; +int gdsqlite_file::checkReservedLock(sqlite3_file *pFile, int *pResOut) { + *pResOut = 0; + return SQLITE_OK; } /* ** No xFileControl() verbs are implemented by this VFS. */ -int gdsqlite_file::fileControl(sqlite3_file *pFile, int op, void *pArg) -{ - return SQLITE_NOTFOUND; +int gdsqlite_file::fileControl(sqlite3_file *pFile, int op, void *pArg) { + return SQLITE_NOTFOUND; } /* ** The xSectorSize() and xDeviceCharacteristics() methods. These two -** may return special values allowing SQLite to optimize file-system +** may return special values allowing SQLite to optimize file-system ** access to some extent. But it is also safe to simply return 0. */ -int gdsqlite_file::sectorSize(sqlite3_file *pFile) -{ - return 0; +int gdsqlite_file::sectorSize(sqlite3_file *pFile) { + return 0; } -int gdsqlite_file::deviceCharacteristics(sqlite3_file *pFile) -{ - return 0; +int gdsqlite_file::deviceCharacteristics(sqlite3_file *pFile) { + return 0; } diff --git a/src/vfs/gdsqlite_file.h b/src/vfs/gdsqlite_file.h index 7850789..427f8ab 100644 --- a/src/vfs/gdsqlite_file.h +++ b/src/vfs/gdsqlite_file.h @@ -3,30 +3,28 @@ #include -#include #include "./sqlite/sqlite3.h" +#include -namespace godot -{ - struct gdsqlite_file - { - sqlite3_file base; /* Base class. Must be first. */ - Ref file; /* File descriptor */ +namespace godot { +struct gdsqlite_file { + sqlite3_file base; /* Base class. Must be first. */ + Ref file; /* File descriptor */ - static int close(sqlite3_file *pFile); - static int read(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst); - static int write(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst); - static int truncate(sqlite3_file *pFile, sqlite_int64 size); - static int sync(sqlite3_file *pFile, int flags); - static int fileSize(sqlite3_file *pFile, sqlite_int64 *pSize); - static int lock(sqlite3_file *pFile, int eLock); - static int unlock(sqlite3_file *pFile, int eLock); - static int checkReservedLock(sqlite3_file *pFile, int *pResOut); - static int fileControl(sqlite3_file *pFile, int op, void *pArg); - static int sectorSize(sqlite3_file *pFile); - static int deviceCharacteristics(sqlite3_file *pFile); - }; + static int close(sqlite3_file *pFile); + static int read(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst); + static int write(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst); + static int truncate(sqlite3_file *pFile, sqlite_int64 size); + static int sync(sqlite3_file *pFile, int flags); + static int fileSize(sqlite3_file *pFile, sqlite_int64 *pSize); + static int lock(sqlite3_file *pFile, int eLock); + static int unlock(sqlite3_file *pFile, int eLock); + static int checkReservedLock(sqlite3_file *pFile, int *pResOut); + static int fileControl(sqlite3_file *pFile, int op, void *pArg); + static int sectorSize(sqlite3_file *pFile); + static int deviceCharacteristics(sqlite3_file *pFile); +}; -} +} //namespace godot #endif \ No newline at end of file diff --git a/src/vfs/gdsqlite_vfs.cpp b/src/vfs/gdsqlite_vfs.cpp index 9a9d21f..76690b3 100644 --- a/src/vfs/gdsqlite_vfs.cpp +++ b/src/vfs/gdsqlite_vfs.cpp @@ -5,21 +5,20 @@ using namespace godot; /* ** Open a file handle. */ -static int gdsqlite_vfs_open(sqlite3_vfs *pVfs, const char *zName, sqlite3_file *pFile, int flags, int *pOutFlags) -{ +static int gdsqlite_vfs_open(sqlite3_vfs *pVfs, const char *zName, sqlite3_file *pFile, int flags, int *pOutFlags) { static const sqlite3_io_methods gdsqlite_file_io_methods = { - 1, /* iVersion */ - gdsqlite_file::close, /* xClose */ - gdsqlite_file::read, /* xRead */ - gdsqlite_file::write, /* xWrite */ - gdsqlite_file::truncate, /* xTruncate */ - gdsqlite_file::sync, /* xSync */ - gdsqlite_file::fileSize, /* xFileSize */ - gdsqlite_file::lock, /* xLock */ - gdsqlite_file::unlock, /* xUnlock */ - gdsqlite_file::checkReservedLock, /* xCheckReservedLock */ - gdsqlite_file::fileControl, /* xFileControl */ - gdsqlite_file::sectorSize, /* xSectorSize */ + 1, /* iVersion */ + gdsqlite_file::close, /* xClose */ + gdsqlite_file::read, /* xRead */ + gdsqlite_file::write, /* xWrite */ + gdsqlite_file::truncate, /* xTruncate */ + gdsqlite_file::sync, /* xSync */ + gdsqlite_file::fileSize, /* xFileSize */ + gdsqlite_file::lock, /* xLock */ + gdsqlite_file::unlock, /* xUnlock */ + gdsqlite_file::checkReservedLock, /* xCheckReservedLock */ + gdsqlite_file::fileControl, /* xFileControl */ + gdsqlite_file::sectorSize, /* xSectorSize */ gdsqlite_file::deviceCharacteristics, /* xDeviceCharacteristics */ }; gdsqlite_file *p = reinterpret_cast(pFile); @@ -35,29 +34,21 @@ static int gdsqlite_vfs_open(sqlite3_vfs *pVfs, const char *zName, sqlite3_file */ /* Convert SQLite's flags to something Godot might understand! */ - if (flags & SQLITE_OPEN_READONLY) - { + if (flags & SQLITE_OPEN_READONLY) { // UtilityFunctions::print("READ"); godot_flags = FileAccess::READ; } // TODO: Figure out if checking for SQLITE_OPEN_READWRITE is necessary when the database is readonly? - if (flags & SQLITE_OPEN_READWRITE) - { - if (flags & SQLITE_OPEN_CREATE) - { - if (file->file_exists(String(zName))) - { + if (flags & SQLITE_OPEN_READWRITE) { + if (flags & SQLITE_OPEN_CREATE) { + if (file->file_exists(String(zName))) { // UtilityFunctions::print("READ WRITE"); godot_flags = FileAccess::READ_WRITE; - } - else - { + } else { // UtilityFunctions::print("WRITE READ"); godot_flags = FileAccess::WRITE_READ; } - } - else - { + } else { // UtilityFunctions::print("READ WRITE"); godot_flags = FileAccess::READ_WRITE; } @@ -66,8 +57,7 @@ static int gdsqlite_vfs_open(sqlite3_vfs *pVfs, const char *zName, sqlite3_file /* Attempt to open the database or journal file using Godot's `open()`-function */ file = FileAccess::open(String(zName), godot_flags); Error err_code = FileAccess::get_open_error(); - if (err_code != Error::OK) - { + if (err_code != Error::OK) { /* File can't be opened! */ /* In most cases this is caused by the fact that Godot opens files in a non-shareable way, as discussed here: */ /* https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/fopen-s-wfopen-s?view=msvc-160 */ @@ -77,8 +67,7 @@ static int gdsqlite_vfs_open(sqlite3_vfs *pVfs, const char *zName, sqlite3_file return SQLITE_CANTOPEN; } - if (pOutFlags) - { + if (pOutFlags) { *pOutFlags = flags; } p->file = file; @@ -91,8 +80,7 @@ static int gdsqlite_vfs_open(sqlite3_vfs *pVfs, const char *zName, sqlite3_file ** is non-zero, then ensure the file-system modification to delete the ** file has been synced to disk before returning. */ -static int gdsqlite_vfs_delete(sqlite3_vfs *pVfs, const char *zPath, int dirSync) -{ +static int gdsqlite_vfs_delete(sqlite3_vfs *pVfs, const char *zPath, int dirSync) { String base_dir = String(zPath).get_base_dir(); Ref dir = DirAccess::open(base_dir); Error err_code = dir->remove(zPath); @@ -104,32 +92,30 @@ static int gdsqlite_vfs_delete(sqlite3_vfs *pVfs, const char *zPath, int dirSync ** Query the file-system to see if the named file exists, is readable or ** is both readable and writable. */ -static int gdsqlite_vfs_access(sqlite3_vfs *pVfs, const char *zPath, int flags, int *pResOut) -{ +static int gdsqlite_vfs_access(sqlite3_vfs *pVfs, const char *zPath, int flags, int *pResOut) { Ref file; Error err_code = Error::OK; - switch (flags) - { - case SQLITE_ACCESS_EXISTS: - *pResOut = file->file_exists(zPath); - break; + switch (flags) { + case SQLITE_ACCESS_EXISTS: + *pResOut = file->file_exists(zPath); + break; - case SQLITE_ACCESS_READWRITE: - file = FileAccess::open(zPath, FileAccess::READ_WRITE); - err_code = FileAccess::get_open_error(); - *pResOut = (err_code == Error::OK); - break; + case SQLITE_ACCESS_READWRITE: + file = FileAccess::open(zPath, FileAccess::READ_WRITE); + err_code = FileAccess::get_open_error(); + *pResOut = (err_code == Error::OK); + break; - case SQLITE_ACCESS_READ: - file = FileAccess::open(zPath, FileAccess::READ); - err_code = FileAccess::get_open_error(); - *pResOut = (err_code == Error::OK); - break; + case SQLITE_ACCESS_READ: + file = FileAccess::open(zPath, FileAccess::READ); + err_code = FileAccess::get_open_error(); + *pResOut = (err_code == Error::OK); + break; - default: - /* Probably throw some kind of error here? */ - break; + default: + /* Probably throw some kind of error here? */ + break; } return SQLITE_OK; @@ -146,13 +132,10 @@ static int gdsqlite_vfs_access(sqlite3_vfs *pVfs, const char *zPath, int flags, ** 1. Path components are separated by a '/'. and ** 2. Full paths begin with a '/' character. */ -static int gdsqlite_vfs_fullPathname(sqlite3_vfs *pVfs, const char *zPath, int nPathOut, char *zPathOut) -{ - for (int i = 0; i < nPathOut; ++i) - { +static int gdsqlite_vfs_fullPathname(sqlite3_vfs *pVfs, const char *zPath, int nPathOut, char *zPathOut) { + for (int i = 0; i < nPathOut; ++i) { zPathOut[i] = zPath[i]; - if (zPath[i] == '\0') - { + if (zPath[i] == '\0') { break; } } @@ -172,21 +155,17 @@ static int gdsqlite_vfs_fullPathname(sqlite3_vfs *pVfs, const char *zPath, int n ** extensions compiled as shared objects. This simple VFS does not support ** this functionality, so the following functions are no-ops. */ -static void *gdsqlite_vfs_dlOpen(sqlite3_vfs *vfs, const char *filename) -{ +static void *gdsqlite_vfs_dlOpen(sqlite3_vfs *vfs, const char *filename) { return 0; } -static void gdsqlite_vfs_dlError(sqlite3_vfs *vfs, int nBytes, char *errMsg) -{ +static void gdsqlite_vfs_dlError(sqlite3_vfs *vfs, int nBytes, char *errMsg) { sqlite3_snprintf(nBytes, errMsg, "Loadable extensions are not supported"); errMsg[nBytes - 1] = '\0'; } -static void (*gdsqlite_vfs_dlSym(sqlite3_vfs *vfs, void *data, const char *symbol))(void) -{ +static void (*gdsqlite_vfs_dlSym(sqlite3_vfs *vfs, void *data, const char *symbol))(void) { return 0; } -static void gdsqlite_vfs_dlClose(sqlite3_vfs *vfs, void *data) -{ +static void gdsqlite_vfs_dlClose(sqlite3_vfs *vfs, void *data) { return; } @@ -194,11 +173,9 @@ static void gdsqlite_vfs_dlClose(sqlite3_vfs *vfs, void *data) ** Parameter zByte points to a buffer nByte bytes in size. Populate this ** buffer with pseudo-random data. */ -static int gdsqlite_vfs_randomness(sqlite3_vfs *pVfs, int nByte, char *zByte) -{ +static int gdsqlite_vfs_randomness(sqlite3_vfs *pVfs, int nByte, char *zByte) { srand(Time::get_singleton()->get_unix_time_from_system()); - for (int i = 0; i < nByte; ++i) - { + for (int i = 0; i < nByte; ++i) { zByte[i] = rand(); } return SQLITE_OK; @@ -208,8 +185,7 @@ static int gdsqlite_vfs_randomness(sqlite3_vfs *pVfs, int nByte, char *zByte) ** Sleep for at least nMicro microseconds. Return the (approximate) number ** of microseconds slept for. */ -static int gdsqlite_vfs_sleep(sqlite3_vfs *pVfs, int nMicro) -{ +static int gdsqlite_vfs_sleep(sqlite3_vfs *pVfs, int nMicro) { OS::get_singleton()->delay_usec(nMicro); return nMicro; } @@ -225,21 +201,18 @@ static int gdsqlite_vfs_sleep(sqlite3_vfs *pVfs, int nMicro) ** value, it will stop working some time in the year 2038 AD (the so-called ** "year 2038" problem that afflicts systems that store time this way). */ -static int gdsqlite_vfs_currentTime(sqlite3_vfs *vfs, double *pTime) -{ +static int gdsqlite_vfs_currentTime(sqlite3_vfs *vfs, double *pTime) { uint64_t unix_time = Time::get_singleton()->get_unix_time_from_system(); *pTime = unix_time / 86400.0 + 2440587.5; return SQLITE_OK; } -static int gdsqlite_vfs_getLastError(sqlite3_vfs *vfs, int nBuf, char *buf) -{ +static int gdsqlite_vfs_getLastError(sqlite3_vfs *vfs, int nBuf, char *buf) { // TODO: Implement properly return 0; } -static int gdsqlite_vfs_currentTimeInt64(sqlite3_vfs *vfs, sqlite3_int64 *now) -{ +static int gdsqlite_vfs_currentTimeInt64(sqlite3_vfs *vfs, sqlite3_int64 *now) { uint64_t unix_time = Time::get_singleton()->get_unix_time_from_system(); *now = unix_time + 210866760000; // Add the number of ms since julian time return SQLITE_OK; @@ -251,31 +224,30 @@ static int gdsqlite_vfs_currentTimeInt64(sqlite3_vfs *vfs, sqlite3_int64 *now) ** ** sqlite3_vfs_register(gdsqlite_vfs(), 0); */ -sqlite3_vfs *godot::gdsqlite_vfs() -{ +sqlite3_vfs *godot::gdsqlite_vfs() { static sqlite3_vfs godot_vfs = { - 3, /* iVersion */ - sizeof(gdsqlite_file), /* szOsFile */ - MAXPATHNAME, /* mxPathname */ - 0, /* pNext */ - "godot", /* zName */ - NULL, /* pAppData */ - gdsqlite_vfs_open, /* xOpen */ - gdsqlite_vfs_delete, /* xDelete */ - gdsqlite_vfs_access, /* xAccess */ - gdsqlite_vfs_fullPathname, /* xFullPathname */ - gdsqlite_vfs_dlOpen, /* xDlOpen */ - gdsqlite_vfs_dlError, /* xDlError */ - gdsqlite_vfs_dlSym, /* xDlSym */ - gdsqlite_vfs_dlClose, /* xDlClose */ - gdsqlite_vfs_randomness, /* xRandomness */ - gdsqlite_vfs_sleep, /* xSleep */ - gdsqlite_vfs_currentTime, /* xCurrentTime */ - gdsqlite_vfs_getLastError, /* xGetLastError */ + 3, /* iVersion */ + sizeof(gdsqlite_file), /* szOsFile */ + MAXPATHNAME, /* mxPathname */ + 0, /* pNext */ + "godot", /* zName */ + NULL, /* pAppData */ + gdsqlite_vfs_open, /* xOpen */ + gdsqlite_vfs_delete, /* xDelete */ + gdsqlite_vfs_access, /* xAccess */ + gdsqlite_vfs_fullPathname, /* xFullPathname */ + gdsqlite_vfs_dlOpen, /* xDlOpen */ + gdsqlite_vfs_dlError, /* xDlError */ + gdsqlite_vfs_dlSym, /* xDlSym */ + gdsqlite_vfs_dlClose, /* xDlClose */ + gdsqlite_vfs_randomness, /* xRandomness */ + gdsqlite_vfs_sleep, /* xSleep */ + gdsqlite_vfs_currentTime, /* xCurrentTime */ + gdsqlite_vfs_getLastError, /* xGetLastError */ gdsqlite_vfs_currentTimeInt64, /* xCurrentTimeInt64 */ - NULL, /* xSetSystemCall */ - NULL, /* xGetSystemCall */ - NULL /* xNextSystemCall */ + NULL, /* xSetSystemCall */ + NULL, /* xGetSystemCall */ + NULL /* xNextSystemCall */ }; return &godot_vfs; } diff --git a/src/vfs/gdsqlite_vfs.h b/src/vfs/gdsqlite_vfs.h index ea36c92..76da0f2 100644 --- a/src/vfs/gdsqlite_vfs.h +++ b/src/vfs/gdsqlite_vfs.h @@ -4,23 +4,22 @@ #include #include -#include #include +#include -#include -#include #include "./sqlite/sqlite3.h" #include "gdsqlite_file.h" +#include +#include /* ** The maximum pathname length supported by this VFS. */ #define MAXPATHNAME 512 -namespace godot -{ +namespace godot { - sqlite3_vfs* gdsqlite_vfs(); +sqlite3_vfs *gdsqlite_vfs(); }