diff --git a/.github/workflows/build_var.json b/.github/workflows/build_var.json index 5d475ee7..7cc594ac 100644 --- a/.github/workflows/build_var.json +++ b/.github/workflows/build_var.json @@ -3,6 +3,7 @@ "template_debug", "template_release" ], + "common_flags": "enable_fts5=yes", "jobs": [ { "name": "Windows (x86_64, MSVC)", diff --git a/.github/workflows/create-build-matrix.ps1 b/.github/workflows/create-build-matrix.ps1 index 8dd67258..6f079e96 100644 --- a/.github/workflows/create-build-matrix.ps1 +++ b/.github/workflows/create-build-matrix.ps1 @@ -1,18 +1,26 @@ $RawMatrix = Get-Content -Raw -Path .github/workflows/build_var.json | ConvertFrom-Json $Targets = $RawMatrix.targets +$CommonFlags = $RawMatrix.common_flags $Jobs = $RawMatrix.jobs $Matrix = @() -foreach ($job in $Jobs) -{ +foreach ($job in $Jobs) { if ($job.skip -eq $true) { continue } - foreach ($target in $Targets) - { + foreach ($target in $Targets) { $MatrixJob = $job.PsObject.Copy() $MatrixJob | Add-Member -MemberType NoteProperty -Name 'target' -Value $target + # Add the common flags to the job-specific flags + if ($null -ne $CommonFlags) { + if ($null -eq $MatrixJob.flags) { + $MatrixJob | Add-Member -MemberType NoteProperty -Name 'flags' -Value $CommonFlags + } + else { + $MatrixJob.flags += " " + $CommonFlags + } + } $Matrix += $MatrixJob } } diff --git a/README.md b/README.md index d2f0785a..0cbb7e34 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,17 @@ Additionally, a video tutorial by [Mitch McCollum (finepointcgi)](https://github Check if the given database connection is or is not in autocommit mode, see [here](https://sqlite.org/c3ref/get_autocommit.html). +- int compileoption_used = **compileoption_used(** String option_name **)** + + Check if the binary was compiled using the specified option, see [here](https://sqlite.org/c3ref/compileoption_get.html). + + Mostly relevant for checking if the [SQLite FTS5 Extension](https://sqlite.org/fts5.html) is enabled, in which case the following lines can be used: + + ```gdscript + db.compileoption_used("SQLITE_ENABLE_FTS5") # Returns '1' if enabled or '0' if disabled + db.compileoption_used("ENABLE_FTS5") # The "SQLITE_"-prefix may be omitted. + ``` + - Boolean success = **backup_to(** String destination_path **)** - Boolean success = **restore_from(** String source_path **)** @@ -297,6 +308,43 @@ Follow the steps described in the Godot documentation as found [here](https://do ldd --version ``` +### 5. Does this plugin support the FTS5 Extension? + +Yes, the [SQLite FTS5 Extension](https://sqlite.org/fts5.html) is supported by this plugin, although it requires the user to re-compile the plugin. + +To re-compile the plugin with FTS5 enabled, follow the instructions as defined in the 'How to contribute?'-section below. +Depending on your choice, following modifications have to be made: + +#### A. Using your own device + +Add the `enable_fts5`-flag to the compilation command: + +``` +scons platform= target_path= target_name=libgdsqlite enable_fts5=yes +``` + +#### B. Using Github Actions + +Update the `common_flags`-field of the `.github/workflows/build_var.json`-file, as follows: + +```json +"common_flags": "enable_fts5=no" +``` + +```json +"common_flags": "enable_fts5=yes" +``` + +Afterwards, push a new commit (containing this change) to the `master`-branch of your forked remote. This commit triggers a new workflow that re-compiles the plugin's binaries with FTS5 enabled. + +### 6. Does this plugin support some kind of encryption? + +Database encryption of any kind is **not** supported in this plugin. Nor are there any plans to support this feature in an upcoming or future release. + +The addition of database encryption might be up for reconsideration if, and only if, a future release of SQLite introduces native support of this feature without requiring the purchase of a license. + +***NOTE**: The natively supported [SQLite Encryption Extension (SEE)](https://sqlite.org/com/see.html) is not applicable as it requires the purchase of a license for the one-time fee of 2000$* + # How to export? The exporting strategy is dependent on the nature of your database. diff --git a/SConstruct b/SConstruct index fca9ccba..e080d689 100644 --- a/SConstruct +++ b/SConstruct @@ -7,6 +7,11 @@ target_name = ARGUMENTS.pop("target_name", "libgdsqlite") env = SConscript("godot-cpp/SConstruct") +env_vars = Variables() +env_vars.Add(BoolVariable("enable_fts5", "Enable SQLite's FTS5 extension which provides full-test search functionality to database applications", False)) +env_vars.Update(env) +Help(env_vars.GenerateHelpText(env)) + target = "{}{}".format( target_path, target_name ) @@ -39,5 +44,11 @@ else: env["SHLIBSUFFIX"] ) +if env["enable_fts5"]: + print("FTS5 is enabled.") + env.Append(CPPDEFINES=['SQLITE_ENABLE_FTS5']) +else: + print("FTS5 is disabled.") + library = env.SharedLibrary(target=target, source=sources) Default(library) \ No newline at end of file diff --git a/demo/database.gd b/demo/database.gd index cbd38f9d..ce967fca 100644 --- a/demo/database.gd +++ b/demo/database.gd @@ -39,6 +39,7 @@ func _ready(): example_of_blob_io() example_of_read_only_database() example_of_database_persistency() + example_of_fts5_usage() func cprint(text : String) -> void: print(text) @@ -435,3 +436,37 @@ func example_of_database_persistency(): # Close the current database db.close_db() + +var fts5_table_name := "posts" + +# Basic example that showcases seaching functionalities of FTS5... +func example_of_fts5_usage(): + db = SQLite.new() + if not db.compileoption_used("ENABLE_FTS5"): + cprint("No support for FTS5 available in binaries (re-compile with compile option `enable_fts5=yes`)") + return + + db.path = db_name + db.verbosity_level = verbosity_level + # Open the database using the db_name found in the path variable + db.open_db() + db.drop_table(fts5_table_name) + + db.query("CREATE VIRTUAL TABLE " + fts5_table_name + " USING FTS5(title, body);") + + var row_array := [ + {"title":'Learn SQlite FTS5', "body":'This tutorial teaches you how to perform full-text search in SQLite using FTS5'}, + {"title":'Advanced SQlite Full-text Search', "body":'Show you some advanced techniques in SQLite full-text searching'}, + {"title":'SQLite Tutorial', "body":'Help you learn SQLite quickly and effectively'}, + ] + + db.insert_rows(fts5_table_name, row_array) + + db.query("SELECT * FROM " + fts5_table_name + " WHERE posts MATCH 'fts5';") + cprint("result: {0}".format([str(db.query_result)])) + + db.query("SELECT * FROM " + fts5_table_name + " WHERE posts MATCH 'learn SQLite';") + cprint("result: {0}".format([str(db.query_result)])) + + # Close the current database + db.close_db() diff --git a/src/gdsqlite.cpp b/src/gdsqlite.cpp index 9422446e..945535e8 100644 --- a/src/gdsqlite.cpp +++ b/src/gdsqlite.cpp @@ -28,6 +28,7 @@ void SQLite::_bind_methods() { 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("compileoption_used"), &SQLite::compileoption_used); // Properties. ClassDB::bind_method(D_METHOD("set_last_insert_rowid", "last_insert_rowid"), &SQLite::set_last_insert_rowid); @@ -787,7 +788,12 @@ bool SQLite::import_from_json(String import_path) { /* 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(); +#ifdef SQLITE_ENABLE_FTS5 + /* FTS5 creates a bunch of shadow tables that cannot be dropped manually! */ + /* The virtual table is responsible for dropping these tables itself */ + remove_shadow_tables(old_database_array); +#endif + int64_t old_number_of_tables = old_database_array.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]; @@ -853,8 +859,12 @@ bool SQLite::import_from_json(String import_path) { 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); +#ifdef SQLITE_ENABLE_FTS5 + /* FTS5 creates a bunch of shadow tables that should NOT be exported! */ + remove_shadow_tables(database_array); +#endif + int64_t number_of_objects = database_array.size(); /* 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]; @@ -986,6 +996,44 @@ bool SQLite::validate_json(const Array &database_array, std::vector &tables_to_import); bool validate_table_dict(const Dictionary &p_table_dict); int backup_database(sqlite3 *source_db, sqlite3 *destination_db); + void remove_shadow_tables(Array &p_array); sqlite3 *db; std::vector> function_registry; @@ -89,6 +90,7 @@ class SQLite : public RefCounted { bool export_to_json(String export_path); int get_autocommit() const; + int compileoption_used(const String &option_name) const; // Properties. void set_last_insert_rowid(const int64_t &p_last_insert_rowid);