Skip to content

Commit

Permalink
Merge branch 'feature/add-optional-fts5'
Browse files Browse the repository at this point in the history
  • Loading branch information
2shady4u committed Aug 13, 2024
2 parents f00cf2b + 57d3fdf commit bec3694
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 6 deletions.
1 change: 1 addition & 0 deletions .github/workflows/build_var.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"template_debug",
"template_release"
],
"common_flags": "enable_fts5=yes",
"jobs": [
{
"name": "Windows (x86_64, MSVC)",
Expand Down
16 changes: 12 additions & 4 deletions .github/workflows/create-build-matrix.ps1
Original file line number Diff line number Diff line change
@@ -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
}
}
Expand Down
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 **)**
Expand Down Expand Up @@ -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=<platform> target_path=<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.
Expand Down
11 changes: 11 additions & 0 deletions SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down Expand Up @@ -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)
35 changes: 35 additions & 0 deletions demo/database.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
58 changes: 56 additions & 2 deletions src/gdsqlite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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<Dictionary> 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];
Expand Down Expand Up @@ -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<Dictionary> 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];
Expand Down Expand Up @@ -986,6 +996,44 @@ bool SQLite::validate_json(const Array &database_array, std::vector<object_struc
return true;
}

void SQLite::remove_shadow_tables(Array &p_array) {
/* The rootpage of virtual tables is always zero!*/
query(String("SELECT name FROM sqlite_master WHERE type = 'table' AND rootpage = 0;"));
int number_of_objects = query_result.size();
Array database_array = query_result.duplicate(true);

/* Make an array of all the expected shadow table names */
Array shadow_table_names = Array();
for (int i = 0; i <= number_of_objects - 1; i++) {
Dictionary object_dict = database_array[i];
String virtual_table_name = object_dict["name"];

/* These are the shadow tables created by FTS5, as discussed here: */
/* https://www.sqlite.org/fts5.html */
shadow_table_names.append(virtual_table_name + "_config");
shadow_table_names.append(virtual_table_name + "_content");
shadow_table_names.append(virtual_table_name + "_data");
shadow_table_names.append(virtual_table_name + "_docsize");
shadow_table_names.append(virtual_table_name + "_idx");
}

/* Get rid of all the shadow tables */
Array clean_array = Array();
for (int i = 0; i <= p_array.size() - 1; i++) {
Dictionary object_dict = p_array[i];
if (object_dict["type"] == String("table")) {
String table_name = object_dict["name"];
if (!shadow_table_names.has(table_name)) {
clean_array.append(object_dict);
}
} else if (object_dict["type"] == String("trigger")) {
clean_array.append(object_dict);
}
}

p_array = clean_array;
}

// Properties.
void SQLite::set_last_insert_rowid(const int64_t &p_last_insert_rowid) {
if (db) {
Expand Down Expand Up @@ -1068,3 +1116,9 @@ int SQLite::get_autocommit() const {
/* Return the default value */
return 1; // A non-zero value indicates the autocommit is on!
}

int SQLite::compileoption_used(const String &option_name) const {
const CharString dummy_name = option_name.utf8();
const char *char_name = dummy_name.get_data();
return sqlite3_compileoption_used(char_name);
}
2 changes: 2 additions & 0 deletions src/gdsqlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class SQLite : public RefCounted {
bool validate_json(const Array &import_json, std::vector<object_struct> &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<std::unique_ptr<Callable>> function_registry;
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit bec3694

Please sign in to comment.