[shotwell/wip/phako/new-database: 2/3] WIP
- From: Jens Georg <jensgeorg src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [shotwell/wip/phako/new-database: 2/3] WIP
- Date: Sat, 22 Feb 2020 08:52:37 +0000 (UTC)
commit e397539fcb77db58632f5ef1a14f2c650b42c660
Author: Jens Georg <mail jensge org>
Date: Thu Feb 20 23:39:36 2020 +0100
WIP
src/db/DatabaseTable.vala | 87 +++------
src/db/VideoTable.vala | 116 ++++++------
src/db/librygel-db/collate.c | 49 +++++
src/db/librygel-db/database-cursor.vala | 195 +++++++++++++++++++
src/db/librygel-db/database.vala | 319 ++++++++++++++++++++++++++++++++
src/db/librygel-db/meson.build | 22 +++
src/db/librygel-db/rygel-db-2.6.deps | 2 +
src/db/librygel-db/sql-function.vala | 31 ++++
src/db/librygel-db/sql-operator.vala | 71 +++++++
src/meson.build | 3 +
10 files changed, 778 insertions(+), 117 deletions(-)
---
diff --git a/src/db/DatabaseTable.vala b/src/db/DatabaseTable.vala
index 6e1a5b2f..6bac30e8 100644
--- a/src/db/DatabaseTable.vala
+++ b/src/db/DatabaseTable.vala
@@ -23,60 +23,44 @@ public abstract class DatabaseTable {
***/
public const int SCHEMA_VERSION = 22;
- protected static Sqlite.Database db;
+ protected static Rygel.Database.Database db;
private static int in_transaction = 0;
public string table_name = null;
private static void prepare_db(string filename) {
- // Open DB.
- int res = Sqlite.Database.open_v2(filename, out db, Sqlite.OPEN_READWRITE | Sqlite.OPEN_CREATE,
- null);
- if (res != Sqlite.OK)
- AppWindow.panic(_("Unable to open/create photo database %s: error code %d").printf(filename,
- res));
-
- // Check if we have write access to database.
- if (filename != Db.IN_MEMORY_NAME) {
- try {
- File file_db = File.new_for_path(filename);
- FileInfo info = file_db.query_info(FileAttribute.ACCESS_CAN_WRITE, FileQueryInfoFlags.NONE);
- if (!info.get_attribute_boolean(FileAttribute.ACCESS_CAN_WRITE))
- AppWindow.panic(_("Unable to write to photo database file:\n %s").printf(filename));
- } catch (Error e) {
- AppWindow.panic(_("Error accessing database file:\n %s\n\nError was: \n%s").printf(filename,
- e.message));
+ try {
+ db = new Rygel.Database.Database(filename, Rygel.Database.Flavor.FOREIGN);
+ // Check if we have write access to database.
+ if (filename != Db.IN_MEMORY_NAME) {
+ try {
+ File file_db = File.new_for_path(filename);
+ FileInfo info = file_db.query_info(FileAttribute.ACCESS_CAN_WRITE,
FileQueryInfoFlags.NONE);
+ if (!info.get_attribute_boolean(FileAttribute.ACCESS_CAN_WRITE))
+ AppWindow.panic(_("Unable to write to photo database file:\n %s").printf(filename));
+ } catch (Error e) {
+ AppWindow.panic(_("Error accessing database file:\n %s\n\nError was:
\n%s").printf(filename,
+ e.message));
+ }
}
- }
-
- unowned string? sql_debug = Environment.get_variable
- ("SHOTWELL_SQL_DEBUG");
-
- if (sql_debug != null && sql_debug != "") {
- db.trace (on_trace);
+ } catch (Rygel.Database.DatabaseError err) {
+ AppWindow.panic(_("Unable to open/create photo database %s: error code %s").printf(filename,
err.message));
}
}
- public static void on_trace (string message) {
- debug ("SQLITE: %s", message);
- }
-
public static void init(string filename) {
// Open DB.
prepare_db(filename);
-
- // Try a query to make sure DB is intact; if not, try to use the backup
- Sqlite.Statement stmt;
- int res = db.prepare_v2("CREATE TABLE IF NOT EXISTS VersionTable ("
- + "id INTEGER PRIMARY KEY, "
- + "schema_version INTEGER, "
- + "app_version TEXT, "
- + "user_data TEXT NULL"
- + ")", -1, out stmt);
- // Query on db failed, copy over backup and open it
- if(res != Sqlite.OK) {
+ try {
+ db.exec("CREATE TABLE IF NOT EXISTS VersionTable ("
+ + "id INTEGER PRIMARY KEY, "
+ + "schema_version INTEGER, "
+ + "app_version TEXT, "
+ + "user_data TEXT NULL"
+ + ")");
+ } catch (Rygel.Database.DatabaseError err) {
db = null;
string backup_path = filename + ".bak";
@@ -92,12 +76,6 @@ public abstract class DatabaseTable {
AppWindow.panic(_("Unable to restore photo database %s").printf(error.message));
}
}
-
- // disable synchronized commits for performance reasons ... this is not vital, hence we
- // don't error out if this fails
- res = db.exec("PRAGMA synchronous=OFF");
- if (res != Sqlite.OK)
- warning("Unable to disable synchronous mode", res);
}
public static void terminate() {
@@ -119,6 +97,7 @@ public abstract class DatabaseTable {
this.table_name = table_name;
}
+ #if 0
// This method will throw an error on an SQLite return code unless it's OK, DONE, or ROW, which
// are considered normal results.
protected static void throw_error(string method, int res) throws DatabaseError {
@@ -168,20 +147,14 @@ public abstract class DatabaseTable {
throw new DatabaseError.ERROR(msg);
}
}
+ #endif
protected bool exists_by_id(int64 id) {
- Sqlite.Statement stmt;
- int res = db.prepare_v2("SELECT id FROM %s WHERE id=?".printf(table_name), -1, out stmt);
- assert(res == Sqlite.OK);
-
- res = stmt.bind_int64(1, id);
- assert(res == Sqlite.OK);
-
- res = stmt.step();
- if (res != Sqlite.ROW && res != Sqlite.DONE)
+ try {
+ return db.query_value("SELECT count(1) FROM %s where id=?", {(Value)id}) != 0;
+ } catch (Rygel.Database.DatabaseError err) {
fatal("exists_by_id [%s] %s".printf(id.to_string(), table_name), res);
-
- return (res == Sqlite.ROW);
+ }
}
protected bool select_by_id(int64 id, string columns, out Sqlite.Statement stmt) {
diff --git a/src/db/VideoTable.vala b/src/db/VideoTable.vala
index 7bd1bb70..8f7cfa6f 100644
--- a/src/db/VideoTable.vala
+++ b/src/db/VideoTable.vala
@@ -60,43 +60,35 @@ public class VideoTable : DatabaseTable {
private static VideoTable instance = null;
private VideoTable() {
- Sqlite.Statement stmt;
- int res = db.prepare_v2("CREATE TABLE IF NOT EXISTS VideoTable ("
- + "id INTEGER PRIMARY KEY, "
- + "filename TEXT UNIQUE NOT NULL, "
- + "width INTEGER, "
- + "height INTEGER, "
- + "clip_duration REAL, "
- + "is_interpretable INTEGER, "
- + "filesize INTEGER, "
- + "timestamp INTEGER, "
- + "exposure_time INTEGER, "
- + "import_id INTEGER, "
- + "event_id INTEGER, "
- + "md5 TEXT, "
- + "time_created INTEGER, "
- + "rating INTEGER DEFAULT 0, "
- + "title TEXT, "
- + "backlinks TEXT, "
- + "time_reimported INTEGER, "
- + "flags INTEGER DEFAULT 0, "
- + "comment TEXT "
- + ")", -1, out stmt);
- assert(res == Sqlite.OK);
+ try {
+ db.begin();
+ db.exec("CREATE TABLE IF NOT EXISTS VideoTable ("
+ + "id INTEGER PRIMARY KEY, "
+ + "filename TEXT UNIQUE NOT NULL, "
+ + "width INTEGER, "
+ + "height INTEGER, "
+ + "clip_duration REAL, "
+ + "is_interpretable INTEGER, "
+ + "filesize INTEGER, "
+ + "timestamp INTEGER, "
+ + "exposure_time INTEGER, "
+ + "import_id INTEGER, "
+ + "event_id INTEGER, "
+ + "md5 TEXT, "
+ + "time_created INTEGER, "
+ + "rating INTEGER DEFAULT 0, "
+ + "title TEXT, "
+ + "backlinks TEXT, "
+ + "time_reimported INTEGER, "
+ + "flags INTEGER DEFAULT 0, "
+ + "comment TEXT "
+ + ")");
- res = stmt.step();
- if (res != Sqlite.DONE)
- fatal("VideoTable constructor", res);
-
- // index on event_id
- Sqlite.Statement stmt2;
- int res2 = db.prepare_v2("CREATE INDEX IF NOT EXISTS VideoEventIDIndex ON VideoTable (event_id)",
- -1, out stmt2);
- assert(res2 == Sqlite.OK);
-
- res2 = stmt2.step();
- if (res2 != Sqlite.DONE)
- fatal("VideoTable constructor", res2);
+ db.exec("CREATE INDEX IF NOT EXISTS VideoEventIDIndex ON VideoTable (event_id)");
+ db.commit();
+ } catch (Rygel.Database.DatabaseError err) {
+ error("VideoTable: %s", err.message);
+ }
set_table_name("VideoTable");
}
@@ -111,14 +103,15 @@ public class VideoTable : DatabaseTable {
// VideoRow.video_id, event_id, time_created are ignored on input. All fields are set on exit
// with values stored in the database.
public VideoID add(VideoRow video_row) throws DatabaseError {
- Sqlite.Statement stmt;
- int res = db.prepare_v2(
+ db.exec(
"INSERT INTO VideoTable (filename, width, height, clip_duration, is_interpretable, "
+ "filesize, timestamp, exposure_time, import_id, event_id, md5, time_created, title, comment) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
- -1, out stmt);
- assert(res == Sqlite.OK);
+ { (GLib.Value)video_row.filepath,
+ (GLib.Value) video_row.width,
+ (GLib.Value) video_row.height});
+ #if 0
ulong time_created = now_sec();
res = stmt.bind_text(1, video_row.filepath);
@@ -161,36 +154,39 @@ public class VideoTable : DatabaseTable {
video_row.event_id = EventID();
video_row.time_created = (time_t) time_created;
video_row.flags = 0;
-
+ #endif
return video_row.video_id;
}
public bool drop_event(EventID event_id) {
- Sqlite.Statement stmt;
- int res = db.prepare_v2("UPDATE VideoTable SET event_id = ? WHERE event_id = ?", -1, out stmt);
- assert(res == Sqlite.OK);
-
- res = stmt.bind_int64(1, EventID.INVALID);
- assert(res == Sqlite.OK);
- res = stmt.bind_int64(2, event_id.id);
- assert(res == Sqlite.OK);
-
- res = stmt.step();
- if (res != Sqlite.DONE) {
- fatal("VideoTable.drop_event", res);
-
- return false;
+ try {
+ db.exec("UPDATE VideoTable SET event_id = ? WHERE event_id = ?",
+ {(GLib.Value) EventID.INVALID, (GLib.Value) event_id.id });
+
+ return true;
+ } catch (Rygel.Database.DatabaseError err) {
+ error("VideoTable.drop_event: %s", err.message);
}
-
- return true;
+
+ return false;
}
public VideoRow? get_row(VideoID video_id) {
- Sqlite.Statement stmt;
- int res = db.prepare_v2(
+ try {
+ var cursor = db.exec_cursor(
"SELECT filename, width, height, clip_duration, is_interpretable, filesize, timestamp, "
+ "exposure_time, import_id, event_id, md5, time_created, rating, title, backlinks, "
- + "time_reimported, flags, comment FROM VideoTable WHERE id=?",
+ + "time_reimported, flags, comment FROM VideoTable WHERE id=?",
+ {(GLib.Value) video_id.id});
+ var column = 0;
+ VideoRow = new VideoRow();
+ row.video_id = cursor.next();
+ } catch (Rygel.Database.DatabaseError err) {
+ return null;
+ }
+
+ Sqlite.Statement stmt;
+ int res = db.prepare_v2,
-1, out stmt);
assert(res == Sqlite.OK);
diff --git a/src/db/librygel-db/collate.c b/src/db/librygel-db/collate.c
new file mode 100644
index 00000000..210ba4d7
--- /dev/null
+++ b/src/db/librygel-db/collate.c
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2012 Jens Georg <mail jensge org>.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <glib.h>
+
+#if HAVE_UNISTRING
+# include <unistr.h>
+#endif
+
+gint rygel_database_utf8_collate_str (const char *a, gsize alen,
+ const char *b, gsize blen)
+{
+ char *a_str, *b_str;
+ gint result;
+
+ /* Make sure the passed strings are null terminated */
+ a_str = g_strndup (a, alen);
+ b_str = g_strndup (b, blen);
+
+#if HAVE_UNISTRING
+ result = u8_strcoll ((const uint8_t *) a_str, (const uint8_t *) b_str);
+#else
+ return g_utf8_collate (a_str, b_str);
+#endif
+
+ g_free (a_str);
+ g_free (b_str);
+
+ return result;
+}
diff --git a/src/db/librygel-db/database-cursor.vala b/src/db/librygel-db/database-cursor.vala
new file mode 100644
index 00000000..682f7cfc
--- /dev/null
+++ b/src/db/librygel-db/database-cursor.vala
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2011 Jens Georg <mail jensge org>.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using Sqlite;
+
+public class Rygel.Database.Cursor : Object {
+ private Statement statement;
+ private int current_state = -1;
+ private bool dirty = true;
+ private unowned Sqlite.Database db;
+
+ /**
+ * Prepare a SQLite statement from a SQL string
+ *
+ * If @arguments is non-null, it will call bind()
+ *
+ * @param db SQLite database this cursor belongs to
+ * @param sql statement to execute
+ * @param arguments array of values to bind to the SQL statement or null if
+ * none
+ */
+ public Cursor (Sqlite.Database db,
+ string sql,
+ GLib.Value[]? arguments) throws DatabaseError {
+ this.db = db;
+
+ this.throw_if_code_is_error (db.prepare_v2 (sql,
+ -1,
+ out this.statement,
+ null));
+ if (arguments == null) {
+ return;
+ }
+
+ this.bind (arguments);
+ }
+
+ /**
+ * Bind new values to a cursor.
+ *
+ * The cursor will be reset.
+ *
+ * This function uses the type of the GValue passed in values to determine
+ * which _bind function to use.
+ *
+ * Supported types are: int, long, int64, uint64, string and pointer.
+ * Note: The only pointer supported is the null pointer as provided by
+ * Database.@null. This is a special value to bind a column to NULL
+ * @param arguments array of values to bind to the SQL statement or null if
+ * none
+ */
+ public void bind (GLib.Value[]? arguments) throws DatabaseError {
+ this.statement.reset ();
+ this.dirty = true;
+ this.current_state = -1;
+
+ for (var i = 1; i <= arguments.length; ++i) {
+ unowned GLib.Value current_value = arguments[i - 1];
+
+ if (current_value.holds (typeof (int))) {
+ statement.bind_int (i, current_value.get_int ());
+ } else if (current_value.holds (typeof (int64))) {
+ statement.bind_int64 (i, current_value.get_int64 ());
+ } else if (current_value.holds (typeof (uint64))) {
+ statement.bind_int64 (i, (int64) current_value.get_uint64 ());
+ } else if (current_value.holds (typeof (long))) {
+ statement.bind_int64 (i, current_value.get_long ());
+ } else if (current_value.holds (typeof (uint))) {
+ statement.bind_int64 (i, current_value.get_uint ());
+ } else if (current_value.holds (typeof (string))) {
+ statement.bind_text (i, current_value.get_string ());
+ } else if (current_value.holds (typeof (void *))) {
+ if (current_value.peek_pointer () == null) {
+ statement.bind_null (i);
+ } else {
+ assert_not_reached ();
+ }
+ } else {
+ var type = current_value.type ();
+ warning (_("Unsupported type %s"), type.name ());
+ assert_not_reached ();
+ }
+
+ if (this.db.errcode () != Sqlite.OK) {
+ throw new DatabaseError.BIND ("Failed to bind value %d in %s: %s",
+ i,
+ this.statement.sql (),
+ this.db.errmsg ());
+ }
+ }
+ }
+
+ /**
+ * Check if the cursor has more rows left
+ *
+ * @return true if more rows left, false otherwise
+ */
+ public bool has_next () throws DatabaseError {
+ if (this.dirty) {
+ this.current_state = this.statement.step ();
+ this.dirty = false;
+ }
+
+ this.throw_if_code_is_error (this.current_state);
+
+ return this.current_state == Sqlite.ROW || this.current_state == -1;
+ }
+
+ /**
+ * Get the next row of this cursor.
+ *
+ * This function uses pointers instead of unowned because var doesn't work
+ * with unowned.
+ *
+ * @return a pointer to the current row
+ */
+ public Statement* next () throws DatabaseError {
+ this.has_next ();
+ this.throw_if_code_is_error (this.current_state);
+ this.dirty = true;
+
+ return this.statement;
+ }
+
+ // convenience functions for "foreach"
+
+ /**
+ * Return an iterator to the cursor to use with foreach
+ *
+ * @return an iterator wrapping the cursor
+ */
+ public Iterator iterator () {
+ return new Iterator (this);
+ }
+
+ public class Iterator {
+ public Cursor cursor;
+
+ public Iterator (Cursor cursor) {
+ this.cursor = cursor;
+ }
+
+ public bool next () throws DatabaseError {
+ return this.cursor.has_next ();
+ }
+
+ public unowned Statement @get () throws DatabaseError {
+ return this.cursor.next ();
+ }
+ }
+
+ /**
+ * Convert a SQLite return code to a DatabaseError
+ */
+ protected void throw_if_code_is_error (int sqlite_error)
+ throws DatabaseError {
+ switch (sqlite_error) {
+ case Sqlite.OK:
+ case Sqlite.DONE:
+ case Sqlite.ROW:
+ return;
+ default:
+ throw new DatabaseError.SQLITE_ERROR
+ ("SQLite error %d: %s",
+ sqlite_error,
+ this.db.errmsg ());
+ }
+ }
+
+ /**
+ * Check if the last operation on the database was an error
+ */
+ protected void throw_if_db_has_error () throws DatabaseError {
+ this.throw_if_code_is_error (this.db.errcode ());
+ }
+}
diff --git a/src/db/librygel-db/database.vala b/src/db/librygel-db/database.vala
new file mode 100644
index 00000000..6fa34b67
--- /dev/null
+++ b/src/db/librygel-db/database.vala
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2009,2011 Jens Georg <mail jensge org>.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using Sqlite;
+
+namespace Rygel.Database {
+
+ public errordomain DatabaseError {
+ SQLITE_ERROR, /// Error code translated from SQLite
+ OPEN, /// Error while opening database file
+ PREPARE, /// Error while preparing a statement
+ BIND, /// Error while binding values to a statement
+ STEP /// Error while running through a result set
+ }
+
+ public enum Flavor {
+ CACHE, /// Database is a cache (will be placed in XDG_USER_CACHE
+ CONFIG, /// Database is configuration (will be placed in XDG_USER_CONFIG)
+ FOREIGN /// Database is at a custom location
+ }
+
+ public enum Flags {
+ READ_ONLY = 1, /// Database is read-only
+ WRITE_ONLY = 1 << 1, /// Database is write-only
+ /// Database can be read and updated
+ READ_WRITE = READ_ONLY | WRITE_ONLY,
+
+ /// Database is shared between several processes
+ SHARED = 1 << 2;
+ }
+
+ /// Prototype for UTF-8 collation function
+ extern static int utf8_collate_str (uint8[] a, uint8[] b);
+
+ /**
+ * Special GValue to pass to exec or exec_cursor to bind a column to
+ * NULL
+ */
+ public static GLib.Value @null () {
+ GLib.Value v = GLib.Value (typeof (void *));
+ v.set_pointer (null);
+
+ return v;
+ }
+}
+
+/**
+ * This class is a thin wrapper around SQLite's database object.
+ *
+ * It adds statement preparation based on GValue and a cancellable exec
+ * function.
+ */
+public class Rygel.Database.Database : Object, Initable {
+
+ public string name { private get; construct set; }
+ public Flavor flavor { private get; construct set; default = Flavor.CACHE; }
+ public Flags flags {
+ private get;
+ construct set;
+ default = Flags.READ_WRITE;
+ }
+
+ /**
+ * Function to implement the custom SQL function 'contains'
+ */
+ public static void utf8_contains (Sqlite.Context context,
+ Sqlite.Value[] args)
+ requires (args.length == 2) {
+ if (args[0].to_text () == null ||
+ args[1].to_text () == null) {
+ context.result_int (0);
+
+ return;
+ }
+
+ var pattern = Regex.escape_string (args[1].to_text ());
+ if (Regex.match_simple (pattern,
+ args[0].to_text (),
+ RegexCompileFlags.CASELESS)) {
+ context.result_int (1);
+ } else {
+ context.result_int (0);
+ }
+ }
+
+ /**
+ * Function to implement the custom SQLite collation 'CASEFOLD'.
+ *
+ * Uses utf8 case-fold to compare the strings.
+ */
+ public static int utf8_collate (int alen, void* a, int blen, void* b) {
+ // unowned to prevent array copy
+ unowned uint8[] _a = (uint8[]) a;
+ _a.length = alen;
+
+ unowned uint8[] _b = (uint8[]) b;
+ _b.length = blen;
+
+ return utf8_collate_str (_a, _b);
+ }
+
+ private string build_path () {
+ var name_is_path = this.name == ":memory:" ||
+ Path.is_absolute (this.name) ||
+ this.flavor == Flavor.FOREIGN;
+
+ if (!name_is_path) {
+ var dirname = Path.build_filename (
+ this.flavor == Flavor.CACHE
+ ? Environment.get_user_cache_dir ()
+ : Environment.get_user_config_dir (),
+ "rygel");
+ DirUtils.create_with_parents (dirname, 0750);
+
+ return Path.build_filename (dirname, "%s.db".printf (this.name));
+ } else {
+ this.flavor = Flavor.FOREIGN;
+
+ return this.name;
+ }
+ }
+
+ private Sqlite.Database db;
+
+ /**
+ * Connect to a SQLite database file
+ *
+ * @param name Name of the database which is used to create the file-name
+ * @param flavor Specifies the flavor of the database
+ * @param flags How to open the database
+ */
+ public Database (string name,
+ Flavor flavor = Flavor.CACHE,
+ Flags flags = Flags.READ_WRITE)
+ throws DatabaseError, Error {
+ Object (name : name, flavor : flavor, flags : flags);
+ init ();
+ }
+
+ /**
+ * Initialize database. Implemented for Initiable interface.
+ *
+ * @param cancellable a cancellable (unused)
+ * @return true on success, false on error
+ * @throws DatabaseError if anything goes wrong
+ */
+ public bool init (Cancellable? cancellable = null) throws Error {
+ var path = this.build_path ();
+ if (flags == Flags.READ_ONLY) {
+ Sqlite.Database.open_v2 (path, out this.db, Sqlite.OPEN_READONLY);
+ } else {
+ Sqlite.Database.open (path, out this.db);
+ }
+
+ if (this.db.errcode () != Sqlite.OK) {
+ var msg = _("Error while opening SQLite database %s: %s");
+ throw new DatabaseError.OPEN (msg, path, this.db.errmsg ());
+ }
+
+ debug ("Using database file %s", path);
+
+ if (flags != Flags.READ_ONLY) {
+ this.exec ("PRAGMA synchronous = OFF");
+ }
+
+ if (Flags.SHARED in flags) {
+ this.exec ("PRAGMA journal_mode = WAL");
+ } else {
+ this.exec ("PRAGMA temp_store = MEMORY");
+ }
+
+ this.db.create_function ("contains",
+ 2,
+ Sqlite.UTF8,
+ null,
+ Database.utf8_contains,
+ null,
+ null);
+
+ this.db.create_collation ("CASEFOLD",
+ Sqlite.UTF8,
+ Database.utf8_collate);
+
+ unowned string? sql_debug = Environment.get_variable
+ ("SHOTWELL_SQL_DEBUG");
+
+ if (sql_debug != null && sql_debug != "") {
+ this.db.trace (on_trace);
+ }
+
+ return true;
+ }
+
+ private void on_trace (string message) {
+ debug ("SQLITE: %s", message);
+ }
+
+ /**
+ * SQL query function.
+ *
+ * Use for all queries that return a result set.
+ *
+ * @param sql The SQL query to run.
+ * @param arguments Values to bind in the SQL query or null.
+ * @throws DatabaseError if the underlying SQLite operation fails.
+ */
+ public Cursor exec_cursor (string sql,
+ GLib.Value[]? arguments = null)
+ throws DatabaseError {
+ return new Cursor (this.db, sql, arguments);
+ }
+
+ /**
+ * Simple SQL query execution function.
+ *
+ * Use for all queries that don't return anything.
+ *
+ * @param sql The SQL query to run.
+ * @param arguments Values to bind in the SQL query or null.
+ * @throws DatabaseError if the underlying SQLite operation fails.
+ */
+ public void exec (string sql,
+ GLib.Value[]? arguments = null)
+ throws DatabaseError {
+ if (arguments == null) {
+ this.db.exec (sql);
+ if (this.db.errcode () != Sqlite.OK) {
+ var msg = "Failed to run query %s: %s";
+ throw new DatabaseError.SQLITE_ERROR (msg, sql, this.db.errmsg ());
+ }
+
+ return;
+ }
+
+ var cursor = this.exec_cursor (sql, arguments);
+ while (cursor.has_next ()) {
+ cursor.next ();
+ }
+ }
+
+ /**
+ * Execute a SQL query that returns a single number.
+ *
+ * @param sql The SQL query to run.
+ * @param args Values to bind in the SQL query or null.
+ * @return The contents of the first row's column as an int.
+ * @throws DatabaseError if the underlying SQLite operation fails.
+ */
+ public int query_value (string sql,
+ GLib.Value[]? args = null)
+ throws DatabaseError {
+ var cursor = this.exec_cursor (sql, args);
+ var statement = cursor.next ();
+ return statement->column_int (0);
+ }
+
+ /**
+ * Analyze triggers of database
+ */
+ public void analyze () {
+ this.db.exec ("ANALYZE");
+ }
+
+ /**
+ * Start a transaction
+ */
+ public void begin () throws DatabaseError {
+ this.exec ("BEGIN");
+ }
+
+ /**
+ * Commit a transaction
+ */
+ public void commit () throws DatabaseError {
+ this.exec ("COMMIT");
+ }
+
+ /**
+ * Rollback a transaction
+ */
+ public void rollback () {
+ try {
+ this.exec ("ROLLBACK");
+ } catch (DatabaseError error) {
+ critical (_("Failed to roll back transaction: %s"),
+ error.message);
+ }
+ }
+
+ /**
+ * Check for an empty SQLite database.
+ * @return true if the file is an empty SQLite database, false otherwise
+ * @throws DatabaseError if the SQLite meta table does not exist which
+ * usually indicates that the file is not a databsae
+ */
+ public bool is_empty () throws DatabaseError {
+ return this.query_value ("SELECT count(type) FROM " +
+ "sqlite_master WHERE rowid = 1") == 0;
+ }
+}
diff --git a/src/db/librygel-db/meson.build b/src/db/librygel-db/meson.build
new file mode 100644
index 00000000..ec7eaf3e
--- /dev/null
+++ b/src/db/librygel-db/meson.build
@@ -0,0 +1,22 @@
+db_sources = files(
+ 'database-cursor.vala',
+ 'database.vala',
+ 'sql-function.vala',
+ 'sql-operator.vala',
+ 'collate.c'
+)
+
+db_lib = library('rygel-db-2.6', db_sources,
+ dependencies : db_deps + [rygel_core],
+ include_directories: [config_include, include_directories('.')],
+ version: '2.0.4',
+ c_args : ['-DG_LOG_DOMAIN="RygelDb"'],
+ vala_header : 'rygel-db.h',
+ install: true,
+ install_dir : [true, rygel_includedir, true])
+install_data('rygel-db-2.6.deps', install_dir : rygel_vapidir)
+
+# need to add to get the current build dir as include dir
+rygel_db = declare_dependency(include_directories: include_directories('.'),
+ dependencies: db_deps,
+ link_with: db_lib)
diff --git a/src/db/librygel-db/rygel-db-2.6.deps b/src/db/librygel-db/rygel-db-2.6.deps
new file mode 100644
index 00000000..eb088138
--- /dev/null
+++ b/src/db/librygel-db/rygel-db-2.6.deps
@@ -0,0 +1,2 @@
+sqlite3
+gupnp-av-1.0
diff --git a/src/db/librygel-db/sql-function.vala b/src/db/librygel-db/sql-function.vala
new file mode 100644
index 00000000..8f7213a5
--- /dev/null
+++ b/src/db/librygel-db/sql-function.vala
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2010 Jens Georg <mail jensge org>.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+public class Rygel.Database.SqlFunction : SqlOperator {
+ public SqlFunction (string name, string arg) {
+ base (name, arg);
+ }
+
+ public override string to_string () {
+ return "%s(%s,?)".printf (name, arg);
+ }
+}
diff --git a/src/db/librygel-db/sql-operator.vala b/src/db/librygel-db/sql-operator.vala
new file mode 100644
index 00000000..81e869d0
--- /dev/null
+++ b/src/db/librygel-db/sql-operator.vala
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2010 Jens Georg <mail jensge org>.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * Rygel is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+using GUPnP;
+
+public class Rygel.Database.SqlOperator : GLib.Object {
+ protected string name;
+ protected string arg;
+ protected string collate;
+
+ public SqlOperator (string name,
+ string arg,
+ string collate = "") {
+ this.name = name;
+ this.arg = arg;
+ this.collate = collate;
+ }
+
+ public SqlOperator.from_search_criteria_op (SearchCriteriaOp op,
+ string arg,
+ string collate) {
+ string sql = null;
+ switch (op) {
+ case SearchCriteriaOp.EQ:
+ sql = "=";
+ break;
+ case SearchCriteriaOp.NEQ:
+ sql = "!=";
+ break;
+ case SearchCriteriaOp.LESS:
+ sql = "<";
+ break;
+ case SearchCriteriaOp.LEQ:
+ sql = "<=";
+ break;
+ case SearchCriteriaOp.GREATER:
+ sql = ">";
+ break;
+ case SearchCriteriaOp.GEQ:
+ sql = ">=";
+ break;
+ default:
+ assert_not_reached ();
+ }
+
+ this (sql, arg, collate);
+ }
+
+ public virtual string to_string () {
+ return "(%s %s ? %s)".printf (arg, name, collate);
+ }
+}
diff --git a/src/meson.build b/src/meson.build
index b9729445..f2d04457 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -59,6 +59,9 @@ executable('shotwell',
'threads/Workers.vala',
'threads/BackgroundJob.vala',
'threads/Semaphore.vala',
+ 'db/librygel-db/database.vala',
+ 'db/librygel-db/database-cursor.vala',
+ 'db/librygel-db/collate.c',
'db/Db.vala',
'db/DatabaseTable.vala',
'db/PhotoTable.vala',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]