2 * arch-tag: Implementation of RhythmDB libgda/SQLite database
4 * Copyright (C) 2004 Benjamin Otte <otte@gnome.org>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "rhythmdb-gda.h"
24 #include "rhythmdb-query-model.h"
30 static void rhythmdb_gda_class_init (RhythmDBGdaClass
* klass
);
31 static void rhythmdb_gda_init (RhythmDBGda
* shell_player
);
32 static void rhythmdb_gda_finalize (GObject
* object
);
34 static void rhythmdb_gda_load (RhythmDB
* rdb
, gboolean
* die
);
35 static void rhythmdb_gda_save (RhythmDB
* rdb
);
36 static RhythmDBEntry
*rhythmdb_gda_entry_new (RhythmDB
* db
,
37 RhythmDBEntryType type
, const char *uri
);
38 static void rhythmdb_gda_entry_set (RhythmDB
* db
, RhythmDBEntry
* entry
,
39 guint propid
, const GValue
* value
);
40 static void rhythmdb_gda_entry_get (RhythmDB
* db
, RhythmDBEntry
* entry
,
41 guint propid
, GValue
* value
);
42 static void rhythmdb_gda_entry_delete (RhythmDB
* db
, RhythmDBEntry
* entry
);
43 static void rhythmdb_gda_entry_delete_by_type (RhythmDB
* adb
,
44 RhythmDBEntryType type
);
45 static RhythmDBEntry
*rhythmdb_gda_entry_lookup_by_location (RhythmDB
* db
,
47 static void rhythmdb_gda_do_full_query (RhythmDB
* db
, GPtrArray
* query
,
48 GtkTreeModel
* main_model
, gboolean
* cancel
);
49 static gboolean
rhythmdb_gda_evaluate_query (RhythmDB
* adb
, GPtrArray
* query
,
50 RhythmDBEntry
* aentry
);
52 G_DEFINE_TYPE (RhythmDBGda
, rhythmdb_gda
, RHYTHMDB_TYPE
)
55 rhythmdb_gda_class_init (RhythmDBGdaClass
* klass
)
57 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
58 RhythmDBClass
*rhythmdb_class
= RHYTHMDB_CLASS (klass
);
60 object_class
->finalize
= rhythmdb_gda_finalize
;
62 rhythmdb_class
->impl_load
= rhythmdb_gda_load
;
63 rhythmdb_class
->impl_save
= rhythmdb_gda_save
;
64 rhythmdb_class
->impl_entry_new
= rhythmdb_gda_entry_new
;
65 rhythmdb_class
->impl_entry_set
= rhythmdb_gda_entry_set
;
66 rhythmdb_class
->impl_entry_get
= rhythmdb_gda_entry_get
;
67 rhythmdb_class
->impl_entry_delete
= rhythmdb_gda_entry_delete
;
68 rhythmdb_class
->impl_entry_delete_by_type
= rhythmdb_gda_entry_delete_by_type
;
69 rhythmdb_class
->impl_lookup_by_location
=
70 rhythmdb_gda_entry_lookup_by_location
;
71 rhythmdb_class
->impl_evaluate_query
= rhythmdb_gda_evaluate_query
;
72 rhythmdb_class
->impl_do_full_query
= rhythmdb_gda_do_full_query
;
76 rhythmdb_gda_init (RhythmDBGda
* db
)
79 /* FIXME: This is a hack to replace '-' with '_' because of SQL column syntax */
80 for (i
= 0; i
< RHYTHMDB_NUM_PROPERTIES
; i
++) {
81 gchar
*mod
= (gchar
*) rhythmdb_nice_elt_name_from_propid (RHYTHMDB (db
), i
);
83 if (*mod
== '-') *mod
= '_';
88 /* we'll set up the Db in the _new function when we actually know the filename */
92 escape_string (const gchar
*orig
)
96 /* this is the shortest possible version. it's definitely slow */
97 strv
= g_strsplit (orig
, "\"", 0);
98 tmp
= g_strjoinv ("\"\"", strv
);
100 ret
= g_strdup_printf ("\"%s\"", tmp
);
107 dump_model (GdaDataModel
*model
)
111 for (i
= 0; i
< gda_data_model_get_n_rows (model
); i
++) {
112 for (j
= 0; j
< gda_data_model_get_n_columns (model
); j
++) {
113 const GdaValue
*value
= gda_data_model_get_value_at (model
, j
, i
);
116 gchar
*str
= gda_value_stringify (value
);
117 g_print ("(%4u, %4u) - %s (%d)\n", i
, j
, str
, gda_value_get_type (value
));
120 g_print ("(%4u, %4u) - (NULL)\n", i
, j
);
126 GStaticMutex my_mutex
= G_STATIC_MUTEX_INIT
;
128 static GdaDataModel
*
129 execute_query (RhythmDBGda
*db
, const gchar
*query
)
134 g_print ("Executing Query: %s\n", query
);
135 command
= gda_command_new (query
, GDA_COMMAND_TYPE_SQL
, GDA_COMMAND_OPTION_STOP_ON_ERRORS
);
136 g_static_mutex_lock (&my_mutex
);
137 model
= gda_connection_execute_single_command (db
->conn
, command
, NULL
);
138 g_static_mutex_unlock (&my_mutex
);
139 gda_command_free (command
);
143 g_warning ("query '%s' failed", query
);
150 execute_nonquery (RhythmDBGda
*db
, const gchar
*query
)
155 g_print ("Executing NonQuery: %s\n", query
);
156 command
= gda_command_new (query
, GDA_COMMAND_TYPE_SQL
, GDA_COMMAND_OPTION_STOP_ON_ERRORS
);
157 g_static_mutex_lock (&my_mutex
);
158 ret
= gda_connection_execute_non_query (db
->conn
, command
, NULL
) != -1;
159 g_static_mutex_unlock (&my_mutex
);
160 gda_command_free (command
);
162 g_warning ("query '%s' failed", query
);
167 #define TABLE "tracks"
169 ensure_table_exists (RhythmDBGda
*db
)
176 model
= gda_connection_get_schema (db
->conn
, GDA_CONNECTION_SCHEMA_TABLES
, NULL
);
180 for (i
= 0; i
< gda_data_model_get_n_rows (model
); i
++) {
181 const GdaValue
*value
= gda_data_model_get_value_at (model
, i
, 0);
182 if (g_str_equal (gda_value_get_string (value
), TABLE
)) {
183 g_print ("Table %s already exists. Great!\n", TABLE
);
187 /* create the table */
188 s
= g_string_new ("create table " TABLE
" (refcount INTEGER, ");
189 for (i
= 0; i
< RHYTHMDB_NUM_PROPERTIES
; i
++) {
190 GType type
= rhythmdb_get_property_type (RHYTHMDB (db
), i
);
192 g_string_append (s
, ", ");
193 g_string_append (s
, rhythmdb_nice_elt_name_from_propid (RHYTHMDB (db
), i
));
196 g_string_append_printf (s
, " VARCHAR (200)");
199 g_string_append_printf (s
, " BOOLEAN");
202 case G_TYPE_LONG
: /* FIXME */
203 case G_TYPE_UINT64
: /* FIXME */
204 g_string_append_printf (s
, " INTEGER");
208 g_string_append_printf (s
, " FLOAT");
211 g_warning ("unknown type %u", (guint
) type
);
212 g_assert_not_reached ();
217 if (i
== RHYTHMDB_PROP_LOCATION
) {
218 /* location is unique */
219 g_string_append (s
, " UNIQUE");
221 g_string_append (s
, ")");
222 ret
= execute_nonquery (db
, s
->str
);
223 g_string_free (s
, TRUE
);
225 /* refcounting with autodelete (woohoo!) */
226 ret
= execute_nonquery (db
, "create trigger delete_track after update of refcount on "
227 TABLE
" when new.refcount = 0 begin delete from " TABLE
228 " where _rowid_ = new._rowid_; end");
234 collect_value_for_sql (const GValue
*val
)
238 switch (G_VALUE_TYPE (val
)) {
240 value
= escape_string (g_value_get_string (val
));
243 value
= g_strdup (g_value_get_boolean (val
) ? "\"TRUE\"" : "\"FALSE\"");
246 value
= g_strdup_printf ("%d", g_value_get_int (val
));
249 value
= g_strdup_printf ("%ld", g_value_get_long (val
));
252 value
= g_strdup_printf ("%"G_GUINT64_FORMAT
, g_value_get_uint64 (val
));
255 value
= g_strdup_printf ("%f", g_value_get_float (val
));
258 value
= g_strdup_printf ("%g", g_value_get_double (val
));
261 g_assert_not_reached ();
268 collect_value_from_sql (GValue
*dest
, const GdaValue
*src
)
272 if (gda_value_isa (src
, GDA_VALUE_TYPE_NULL
))
274 g_assert (gda_value_isa (src
, GDA_VALUE_TYPE_STRING
));
275 str
= gda_value_get_string (src
);
277 /* keep in sync with create table */
278 switch (G_VALUE_TYPE (dest
)) {
280 g_value_set_string (dest
, str
);
283 g_value_set_boolean (dest
, g_str_equal (str
, "TRUE"));
286 g_value_set_int (dest
, strtol (str
, NULL
, 10));
289 g_value_set_long (dest
, strtol (str
, NULL
, 10));
292 g_value_set_uint64 (dest
, strtoul (str
, NULL
, 10));
295 g_value_set_float (dest
, atof (str
));
298 g_value_set_double (dest
, atof (str
));
301 g_assert_not_reached ();
307 _initialize (RhythmDBGda
*db
)
309 /* check songs table */
310 if (!ensure_table_exists (db
))
313 return execute_nonquery (db
, "update " TABLE
" set refcount=1");
317 rhythmdb_gda_new (const char *name
)
319 RhythmDBGda
*db
= g_object_new (RHYTHMDB_TYPE_GDA
, "name", name
, NULL
);
320 gchar
*conn_str
= g_strdup_printf ("URI=%s", name
);
322 g_print ("opening Db with conn string: %s\n", conn_str
);
323 db
->client
= gda_client_new ();
324 g_return_val_if_fail (db
->client
, NULL
);
325 db
->conn
= gda_client_open_connection_from_string (db
->client
, "SQLite",
329 g_warning ("GDA: error opening the library");
330 g_object_unref (db
->client
);
334 if (!_initialize (db
)) {
335 g_warning ("GDA: error initializing the library");
336 g_object_unref (db
->conn
);
337 g_object_unref (db
->client
);
342 return RHYTHMDB (db
);
346 rhythmdb_gda_finalize (GObject
* object
)
348 RhythmDBGda
*db
= RHYTHMDB_GDA (object
);
350 g_object_unref (db
->conn
);
351 g_object_unref (db
->client
);
353 G_OJECT_CLASS (rhythmdb_gda_parent_class
)->finalize (object
);
357 rhythmdb_gda_load (RhythmDB
* rdb
, gboolean
* die
)
360 static guint types
[] = { RHYTHMDB_PROP_TITLE
, RHYTHMDB_PROP_ARTIST
,
361 RHYTHMDB_PROP_ALBUM
, RHYTHMDB_PROP_LAST_PLAYED
};
362 RhythmDBGda
*db
= RHYTHMDB_GDA (rdb
);
363 GdaDataModel
*model
= execute_query (db
, "select _rowid_, title, artist, album, last_played from " TABLE
);
364 g_return_if_fail (model
);
366 for (i
= 0; i
< gda_data_model_get_n_rows (model
); i
++) {
367 gpointer entry
= GINT_TO_POINTER ((gint
) strtol (gda_value_get_string (
368 gda_data_model_get_value_at (model
, 0, i
)), NULL
, 10));
369 for (j
= 0; j
< G_N_ELEMENTS (types
); j
++) {
371 g_value_init (&val
, rhythmdb_get_property_type (rdb
, types
[j
]));
372 collect_value_from_sql (&val
, gda_data_model_get_value_at (model
, j
+ 1, i
));
373 rhythmdb_entry_sync_mirrored (rdb
, entry
, types
[j
], &val
);
374 g_value_unset (&val
);
376 rhythmdb_emit_entry_restored (rdb
, entry
);
381 rhythmdb_gda_save (RhythmDB
* rdb
)
383 /* nothing to do here */
386 static RhythmDBEntry
*
387 rhythmdb_gda_entry_new (RhythmDB
* rdb
, RhythmDBEntryType type
, const char *uri
)
389 RhythmDBGda
*db
= RHYTHMDB_GDA (rdb
);
390 gchar
*query
= g_strdup_printf ("insert into " TABLE
391 " (type, refcount, location) values (%d, 1, \"%s\")", (gint
) type
, uri
);
393 if (!execute_nonquery (db
, query
)) {
398 return rhythmdb_gda_entry_lookup_by_location (rdb
, uri
);
402 rhythmdb_gda_entry_set (RhythmDB
* rdb
, RhythmDBEntry
* entry
,
403 guint propid
, const GValue
* value
)
405 RhythmDBGda
*db
= RHYTHMDB_GDA (rdb
);
406 gchar
*collect
= collect_value_for_sql (value
);
407 gchar
*query
= g_strdup_printf ("update " TABLE
" set %s = %s where _rowid_ = %d",
408 rhythmdb_nice_elt_name_from_propid (rdb
, propid
), collect
,
409 GPOINTER_TO_INT (entry
));
411 execute_nonquery (db
, query
);
416 rhythmdb_gda_entry_get (RhythmDB
* rdb
, RhythmDBEntry
* entry
,
417 guint propid
, GValue
* value
)
419 RhythmDBGda
*db
= RHYTHMDB_GDA (rdb
);
420 gchar
*query
= g_strdup_printf ("select %s from " TABLE
421 " where _ROWID_ = %d", rhythmdb_nice_elt_name_from_propid (rdb
, propid
),
422 GPOINTER_TO_INT (entry
));
423 GdaDataModel
*model
= execute_query (db
, query
);
428 if (gda_data_model_get_n_rows (model
) > 0) {
429 g_assert (gda_data_model_get_n_rows (model
) == 1);
430 collect_value_from_sql (value
, gda_data_model_get_value_at (model
, 0, 0));
432 g_object_unref (G_OBJECT (model
));
436 rhythmdb_gda_ref (RhythmDBGda
*db
, gint id
, gint count
)
438 gchar
*query
= g_strdup_printf ("select refcount from " TABLE
439 " where _ROWID_ = %d", id
);
440 GdaDataModel
*model
= execute_query (db
, query
);
445 g_assert (gda_data_model_get_n_rows (model
) == 1);
446 count
+= strtol (gda_value_get_string (
447 gda_data_model_get_value_at (model
, 0, 0)), NULL
, 10);
448 g_object_unref (model
);
450 query
= g_strdup_printf ("update " TABLE
" set refcount = %d where _ROWID_ = %d",
452 execute_nonquery (db
, query
);
457 rhythmdb_gda_entry_delete (RhythmDB
* rdb
, RhythmDBEntry
* entry
)
459 rhythmdb_gda_ref (RHYTHMDB_GDA (rdb
), GPOINTER_TO_INT (entry
), -1);
463 rhythmdb_gda_entry_delete_by_type (RhythmDB
* rdb
, RhythmDBEntryType type
)
465 g_assert_not_reached ();
468 static RhythmDBEntry
*
469 rhythmdb_gda_entry_lookup_by_location (RhythmDB
* rdb
, const char *uri
)
472 RhythmDBGda
*db
= RHYTHMDB_GDA (rdb
);
473 gchar
*escaped_uri
= escape_string (uri
);
474 gchar
*query
= g_strdup_printf ("select _ROWID_ from " TABLE
475 " where location = %s", escaped_uri
);
476 GdaDataModel
*model
= execute_query (db
, query
);
478 g_free (escaped_uri
);
480 if (!model
) return NULL
;
482 if (gda_data_model_get_n_rows (model
) > 0) {
483 g_assert (gda_data_model_get_n_rows (model
) == 1);
484 ret
= GINT_TO_POINTER (strtol (gda_value_get_string (
485 gda_data_model_get_value_at (model
, 0, 0)), NULL
, 10));
489 g_object_unref (G_OBJECT (model
));
490 g_print ("FOUND ENTRY %p\n", ret
);
495 translate_query (RhythmDBGda
*db
, RhythmDBQueryData
*data
)
497 gchar
*operation
= NULL
, *value
, *ret
;
499 switch (data
->type
) {
500 case RHYTHMDB_QUERY_DISJUNCTION
:
501 case RHYTHMDB_QUERY_SUBQUERY
:
502 g_assert_not_reached (); /* FIXME */
504 case RHYTHMDB_QUERY_PROP_EQUALS
:
505 operation
= "%s = %s";
507 case RHYTHMDB_QUERY_PROP_LIKE
:
508 case RHYTHMDB_QUERY_PROP_NOT_LIKE
:
509 g_assert_not_reached (); /* FIXME */
511 case RHYTHMDB_QUERY_PROP_GREATER
:
512 operation
= "%s > %s";
514 case RHYTHMDB_QUERY_PROP_LESS
:
515 operation
= "%s < %s";
517 case RHYTHMDB_QUERY_END
:
519 g_assert_not_reached ();
523 value
= collect_value_for_sql (data
->val
);
524 ret
= g_strdup_printf (operation
, rhythmdb_nice_elt_name_from_propid (RHYTHMDB (db
), data
->propid
),
531 /* set rowid to 0 for all rows */
532 static GdaDataModel
*
533 do_query (RhythmDBGda
*db
, GPtrArray
* query
, gint rowid
)
536 RhythmDBQueryData
*data
;
537 GString
*s
= g_string_new (NULL
);
541 g_assert (query
->len
== 1);
542 g_string_append (s
, "select _ROWID_ from " TABLE
" where ");
544 g_string_append_printf (s
, "rowid == %d AND (", rowid
);
545 for (i
= 0; i
< query
->len
; i
++) {
546 data
= (RhythmDBQueryData
*) g_ptr_array_index (query
, i
);
547 tmp
= translate_query (db
, data
);
549 g_string_append (s
, " and ");
550 g_string_append (s
, tmp
);
554 g_string_append (s
, ")");
556 model
= execute_query (db
, s
->str
);
557 g_string_free (s
, TRUE
);
562 rhythmdb_gda_do_full_query (RhythmDB
* rdb
, GPtrArray
* query
,
563 GtkTreeModel
* main_model
, gboolean
* cancel
)
565 RhythmDBGda
*db
= RHYTHMDB_GDA (rdb
);
566 GdaDataModel
*model
= do_query (db
, query
, 0);
567 g_return_if_fail (model
);
574 queue
= g_ptr_array_sized_new (gda_data_model_get_n_rows (model
));
575 for (j
= 0; j
< gda_data_model_get_n_rows (model
); j
++) {
576 g_ptr_array_add (queue
, GINT_TO_POINTER (strtol (gda_value_get_string (
577 gda_data_model_get_value_at (model
, 0, j
)), NULL
, 10)));
579 rhythmdb_query_model_add_entries (RHYTHMDB_QUERY_MODEL (main_model
), queue
);
584 rhythmdb_gda_evaluate_query (RhythmDB
* rdb
, GPtrArray
* query
,
585 RhythmDBEntry
* aentry
)
588 RhythmDBGda
*db
= RHYTHMDB_GDA (rdb
);
589 GdaDataModel
*model
= do_query (db
, query
, GPOINTER_TO_INT (aentry
));
591 if (!model
) return FALSE
;
592 ret
= gda_data_model_get_n_rows (model
) > 0;
593 g_object_unref (model
);