2 * Copyright (C) 2009,2011 Jens Georg <mail@jensge.org>.
4 * Author: Jens Georg <mail@jensge.org>
6 * This file is part of Rygel.
8 * Rygel is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * Rygel is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 public errordomain Rygel.MediaExport.DatabaseError {
30 namespace Rygel.MediaExport {
31 extern static int utf8_collate_str (uint8[] a, uint8[] b);
35 * This class is a thin wrapper around SQLite's database object.
37 * It adds statement preparation based on GValue and a cancellable exec
40 internal class Rygel.MediaExport.Database : SqliteWrapper {
43 * Function to implement the custom SQL function 'contains'
45 private static void utf8_contains (Sqlite.Context context,
47 requires (args.length == 2) {
48 if (args[0].to_text () == null ||
49 args[1].to_text () == null) {
50 context.result_int (0);
55 var pattern = Regex.escape_string (args[1].to_text ());
56 if (Regex.match_simple (pattern,
58 RegexCompileFlags.CASELESS)) {
59 context.result_int (1);
61 context.result_int (0);
66 * Function to implement the custom SQLite collation 'CASEFOLD'.
68 * Uses utf8 case-fold to compare the strings.
70 private static int utf8_collate (int alen, void* a, int blen, void* b) {
71 // unowned to prevent array copy
72 unowned uint8[] _a = (uint8[]) a;
75 unowned uint8[] _b = (uint8[]) b;
78 return utf8_collate_str (_a, _b);
82 * Open a database in the user's cache directory as defined by XDG
84 * @param name of the database, used to build full path
85 * (<cache-dir>/rygel/<name>.db)
87 public Database (string name) throws DatabaseError {
90 if (name != ":memory:") {
91 var dirname = Path.build_filename (
92 Environment.get_user_cache_dir (),
94 DirUtils.create_with_parents (dirname, 0750);
95 db_file = Path.build_filename (dirname, "%s.db".printf (name));
102 debug ("Using database file %s", db_file);
104 this.exec ("PRAGMA synchronous = OFF");
105 this.exec ("PRAGMA temp_store = MEMORY");
106 this.exec ("PRAGMA count_changes = OFF");
108 this.db.create_function ("contains",
112 Database.utf8_contains,
116 this.db.create_collation ("CASEFOLD",
118 Database.utf8_collate);
122 * SQL query function.
124 * Use for all queries that return a result set.
126 * @param sql The SQL query to run.
127 * @param args Values to bind in the SQL query or null.
128 * @throws DatabaseError if the underlying SQLite operation fails.
130 public DatabaseCursor exec_cursor (string sql,
131 GLib.Value[]? arguments = null)
132 throws DatabaseError {
133 return new DatabaseCursor (this.db, sql, arguments);
137 * Simple SQL query execution function.
139 * Use for all queries that don't return anything.
141 * @param sql The SQL query to run.
142 * @param args Values to bind in the SQL query or null.
143 * @throws DatabaseError if the underlying SQLite operation fails.
145 public void exec (string sql,
146 GLib.Value[]? arguments = null)
147 throws DatabaseError {
148 if (arguments == null) {
149 this.throw_if_code_is_error (this.db.exec (sql));
154 var cursor = this.exec_cursor (sql, arguments);
155 while (cursor.has_next ()) {
161 * Execute a SQL query that returns a single number.
163 * @param sql The SQL query to run.
164 * @param args Values to bind in the SQL query or null.
165 * @return The contents of the first row's column as an int.
166 * @throws DatabaseError if the underlying SQLite operation fails.
168 public int query_value (string sql,
169 GLib.Value[]? args = null)
170 throws DatabaseError {
171 var cursor = this.exec_cursor (sql, args);
172 var statement = cursor.next ();
173 return statement->column_int (0);
177 * Analyze triggers of database
179 public void analyze () {
180 this.db.exec ("ANALYZE");
184 * Special GValue to pass to exec or exec_cursor to bind a column to
187 public static GLib.Value @null () {
188 GLib.Value v = GLib.Value (typeof (void *));
189 v.set_pointer (null);
195 * Start a transaction
197 public void begin () throws DatabaseError {
202 * Commit a transaction
204 public void commit () throws DatabaseError {
205 this.exec ("COMMIT");
209 * Rollback a transaction
211 public void rollback () {
213 this.exec ("ROLLBACK");
214 } catch (DatabaseError error) {
215 critical (_("Failed to roll back transaction: %s"),