267
g_return_val_if_fail (a!=NULL, 0);
267
g_return_val_if_fail (a!=NULL, 0);
268
g_return_val_if_fail (b!=NULL, 0);
268
g_return_val_if_fail (b!=NULL, 0);
269
int result = entry_compare_property(a, b, property);
269
int result = entry_compare_property(a, b, property);
270
reading_trace (4, "compare: %s %i = %s, %s %i = %s: %i.\n", ENTRY_PF(a),
271
entry_property_to_string (a, property), ENTRY_PF(b),
272
entry_property_to_string (b, property), result);
270
entry_unref (a, "musicsourceview::sort-id-list");
273
entry_unref (a, "musicsourceview::sort-id-list");
271
entry_unref (b, "musicsourceview::sort-id-list");
274
entry_unref (b, "musicsourceview::sort-id-list");
275
return result;
278
return result;
281
reading_trace (3, "_sort_result_list: %s.%s\n",
282
entry_type_name[type], schema[type][property].name);
278
// Implemented as quicksort.
284
// Implemented as quicksort.
279
return g_slist_sort(list, (GCompareFunc)compare);
285
GSList *result = g_slist_sort(list, (GCompareFunc)compare);
286
reading_trace (4, "\n");
282
// Finds entries of type described in prev->next_clnode, connected by prev->id
290
// Finds entries of type described in prev->next_clnode, connected by prev->id
498
reading_trace (3, "group_sorts: cnode %s, prev sg %i. %x, %x\n", entry_type_name[cnode->type],
506
reading_trace (3, "group_sorts: cnode %s, prev sg %i. %x, %x\n", entry_type_name[cnode->type],
499
prev->sort_group, prev->clnode,
507
prev->sort_group, prev->clnode,
500
pids_node->next? ((_Result *)pids_node->next->data)->clnode: 0);
508
pids_node->next? ((_Result *)pids_node->next->data)->clnode: 0);
501
reading_trace (4, "\n");
503
GSList *grouped = ungrouped;
510
GSList *grouped = ungrouped;
504
if (prev->sort_group==0) {
511
if (prev->sort_group==0) {
542
// Add this set of results to the current sort group, don't sort anything yet.
549
// Add this set of results to the current sort group, don't sort anything yet.
544
*sort_group = g_slist_concat(*sort_group, ungrouped);
551
*sort_group = g_slist_concat(*sort_group, ungrouped);
552
reading_trace (4, "added %i results to current group %i, new length %i.\n",
553
g_slist_length (ungrouped), *current_sort_group_id,
554
g_slist_length (*sort_group));
545
grouped = NULL;
555
grouped = NULL;
546
} else {
556
} else {
547
// Sort current sort group ('sort_group'), and start a new one ('ungrouped'). This
557
// Sort current sort group ('sort_group'), and start a new one ('ungrouped'). This
548
// code path is also called on the last result, because the sort group needs to be
558
// code path is also called on the last result, because the sort group needs to be
549
// emptied.
559
// emptied.
551
if (last_group) {
561
if (last_group && *current_sort_group_id==prev->sort_group) {
552
*sort_group = g_slist_concat(*sort_group, ungrouped);
562
*sort_group = g_slist_concat(*sort_group, ungrouped);
553
ungrouped = NULL;
563
ungrouped = NULL;
556
g_return_val_if_fail (cnode!=NULL, NULL);
566
g_return_val_if_fail (cnode!=NULL, NULL);
567
reading_trace (4, "sorted and closing group %i, length %i.\n",
568
*current_sort_group_id, g_slist_length (*sort_group));
557
grouped = _sort_result_list(self, *sort_group, cnode->type,
569
grouped = _sort_result_list(self, *sort_group, cnode->type,
558
cnode->sort_property_id, cnode->flags);
570
cnode->sort_property_id, cnode->flags);
572
if (last_group && ungrouped != NULL) {
573
// We won't get called again, so sort the last group now too.
574
ungrouped = _sort_result_list(self, ungrouped, cnode->type,
575
cnode->sort_property_id, cnode->flags);
576
grouped = g_slist_concat (grouped, ungrouped);
560
*sort_group = ungrouped;
580
*sort_group = ungrouped;
561
*current_sort_group_id = prev->sort_group;
581
*current_sort_group_id = prev->sort_group;
584
reading_trace (4, "\n");
565
return grouped;
586
return grouped;
110
int foreign_property_id, int limit);
110
int foreign_property_id, int limit);
111
static GSList *query_entry_children(MusicSource *music_source, int parent_id, EntryType child_entry_type, int child_property);
111
static GSList *query_entry_children(MusicSource *music_source, int parent_id, EntryType child_entry_type, int child_property);
112
static GSList *query_entry_children_ids(MusicSource *music_source, int parent_id, EntryType child_entry_type, int child_property);
112
static GSList *query_entry_children_ids(MusicSource *music_source, int parent_id, EntryType child_entry_type, int child_property);
113
static GSList *query_relations(MusicSource *music_source, EntryType local_type, int local_id, int relation_apid);
113
static GSList *query_relations (MusicSource *music_source, EntryType local_type, int local_id,
115
static GSList *query_relation_ids (MusicSource *music_source, EntryType local_type, int local_id,
114
static GSList *query_ids(MusicSource *source, EntryType entry_type);
117
static GSList *query_ids(MusicSource *source, EntryType entry_type);
115
static GSList *query_matching_except(MusicSource *music_source, Entry *entry, int ignored_apid);
118
static GSList *query_matching_except(MusicSource *music_source, Entry *entry, int ignored_apid);
1746
// Return list of entries of child_entry_type where child_property = parent_id
1749
// Return list of entries of child_entry_type where child_property = parent_id
1747
// FIXME: query_relations could do this.
1750
// FIXME: deprecated; query_relations could do this.
1748
static GSList *query_entry_children (MusicSource *music_source, int parent_id,
1751
static GSList *query_entry_children (MusicSource *music_source, int parent_id,
1749
EntryType child_entry_type, int child_property) {
1752
EntryType child_entry_type, int child_property) {
1750
int rows, columns; char **data;
1753
return query_relations (music_source, schema[child_entry_type][child_property].type, parent_id,
1754
MAKE_APID(child_entry_type, child_property));
1756
/*int rows, columns; char **data;
1751
GSList *list = NULL;
1757
GSList *list = NULL;
1752
data = db_query_printf(LIBRARY(music_source), &rows, &columns, NULL,
1758
data = db_query_printf(LIBRARY(music_source), &rows, &columns, NULL,
1753
"SELECT id FROM _%s WHERE %s_id=%i", entry_type_name[child_entry_type],
1759
"SELECT id FROM _%s WHERE %s_id=%i", entry_type_name[child_entry_type],
1754
schema[child_entry_type][child_property].name, parent_id);
1760
schema[child_entry_type][child_property].name, parent_id);
1755
list = _query_entries(LIBRARY(music_source), child_entry_type, data, rows, columns);
1761
list = _query_entries(LIBRARY(music_source), child_entry_type, data, rows, columns);
1756
sqlite3_free_table (data);
1762
sqlite3_free_table (data);
1757
return list;
1763
return list;*/
1766
/* FIXME: deprecated. Should remove this and use query_relation_ids. */
1760
static GSList *query_entry_children_ids(MusicSource *music_source, int parent_id, EntryType child_entry_type, int child_property) {
1767
static GSList *query_entry_children_ids(MusicSource *music_source, int parent_id, EntryType child_entry_type, int child_property) {
1761
int rows, columns; char **data;
1768
return query_relation_ids (music_source, schema[child_entry_type][child_property].type,
1769
parent_id, MAKE_APID(child_entry_type, child_property));
1771
/*int rows, columns; char **data;
1762
GSList *list=NULL;
1772
GSList *list=NULL;
1763
data = db_query_printf(LIBRARY(music_source), &rows, &columns, NULL,
1773
data = db_query_printf(LIBRARY(music_source), &rows, &columns, NULL,
1764
"SELECT id FROM _%s WHERE %s_id=%i", entry_type_name[child_entry_type],
1774
"SELECT id FROM _%s WHERE %s_id=%i", entry_type_name[child_entry_type],
1778
// of a list anyway.
1788
// of a list anyway.
1779
list=g_slist_reverse(list);
1789
list=g_slist_reverse(list);
1780
sqlite3_free_table(data);
1790
sqlite3_free_table(data);
1781
return list;
1791
return list;*/
1784
static GSList *query_relations (MusicSource *music_source, EntryType local_type,
1794
static char **db_query_relations (Library *self, EntryType local_type, int local_id,
1785
int local_id, int relation_apid) {
1795
int relation_apid, EntryType *p_foreign_type, int *p_rows,
1786
int rows, columns; char **data; GSList *list = NULL;
1796
int *p_columns) {
1789
EntryType foreign_type;
1790
if (APID_GET_TYPE(relation_apid)==local_type) {
1799
if (APID_GET_TYPE(relation_apid)==local_type) {
1791
// * All entries that local refers to in a property:
1800
// * All entries that local refers to in a property:
1792
foreign_type = APID_GET_PROPERTY(relation_apid).type;
1801
const EntryType foreign_type = *p_foreign_type = APID_GET_PROPERTY(relation_apid).type;
1793
data = db_query_printf(LIBRARY(music_source), &rows, &columns, NULL,
1802
1803
/*_db_append_property_query (*foreign_type, APID_GET_PROPERTY(relation_apid).name,
1804
projection, FALSE, joins, join_flags);*/
1806
// FIXME: rewrite using _db_append_property_query, so shadowing works
1807
data = db_query_printf(self, p_rows, p_columns, NULL,
1794
"SELECT _%s.id FROM _%s INNER JOIN _%s ON _%s.%s_id=_%s.id "
1808
"SELECT _%s.id FROM _%s INNER JOIN _%s ON _%s.%s_id=_%s.id "
1795
"WHERE _%s.id=%i", entry_type_name[foreign_type],
1809
"WHERE _%s.id=%i", entry_type_name[foreign_type],
1796
entry_type_name[local_type], entry_type_name[foreign_type],
1810
entry_type_name[local_type], entry_type_name[foreign_type],
1797
entry_type_name[local_type], APID_GET_PROPERTY(relation_apid).name,
1811
entry_type_name[local_type], APID_GET_PROPERTY(relation_apid).name,
1798
entry_type_name[foreign_type], entry_type_name[local_type],
1812
entry_type_name[foreign_type], entry_type_name[local_type],
1799
local_id);
1813
local_id);
1801
} else if (APID_GET_PROPERTY(relation_apid).type==local_type) {
1814
} else if (APID_GET_PROPERTY(relation_apid).type==local_type) {
1802
// * All entries that refer to local in a property
1815
1803
foreign_type = APID_GET_TYPE(relation_apid);
1816
// Query all entries that refer to local in a property.
1804
data = db_query_printf(LIBRARY(music_source), &rows, &columns, NULL,
1817
//
1805
"SELECT _%s.id FROM _%s INNER JOIN _%s ON _%s.%s_id=_%s.id WHERE _%s.id=%i",
1818
GString *relation_property = g_string_new (NULL),
1806
entry_type_name[APID_GET_TYPE(relation_apid)],
1819
*joins = g_string_new (NULL);
1807
entry_type_name[APID_GET_TYPE(relation_apid)],
1820
gboolean join_flags[ENTRY_TYPE_COUNT] = { 0 };
1808
entry_type_name[local_type],
1821
const EntryType foreign_type = *p_foreign_type = APID_GET_TYPE(relation_apid);
1809
entry_type_name[APID_GET_TYPE(relation_apid)],
1822
1810
APID_GET_PROPERTY(relation_apid).name,
1823
//printf ("query relations: %s.%s => %s.%i.\n", APID_NAME(relation_apid),
1811
entry_type_name[local_type],
1824
// entry_type_name[local_type], local_id); fflush (stdout);
1812
entry_type_name[local_type], local_id);
1825
1813
1826
// Set up to query relation_apid. For example, if local entry is artist 3 and relation apid
1827
// is recording.artist, relation_property is:
1828
// COALESCE(_recording.artist_id, _composition.artist_id)
1829
// to find the recordings that link to artist 3, taking into account recording.artist is a
1830
// shadowing property. The joins are also handled by this function.
1831
_db_append_property_query (foreign_type, APID_GET_PROPERTY_ID(relation_apid),
1832
relation_property, FALSE, joins, join_flags);
1834
data = db_query_printf(self, p_rows, p_columns, NULL,
1835
"SELECT _%s.id FROM _%s %s INNER JOIN _%s ON %s=_%s.id WHERE "
1836
"_%s.id=%i", entry_type_name[foreign_type],
1837
entry_type_name[foreign_type], joins->str,
1838
entry_type_name[local_type], relation_property->str,
1839
entry_type_name[local_type], entry_type_name[local_type], local_id);
1815
g_return_val_if_reached(NULL);
1841
g_return_val_if_reached(NULL);
1817
list = _query_entries(LIBRARY(music_source), foreign_type, data, rows, columns);
1843
return data;
1846
static GSList *query_relations (MusicSource *music_source, EntryType local_type,
1847
int local_id, int relation_apid) {
1848
Library *self = LIBRARY(music_source);
1849
EntryType foreign_type; int rows, columns;
1850
char **data = db_query_relations (self, local_type, local_id, relation_apid, &foreign_type,
1853
GSList *list = _query_entries (self, foreign_type, data, rows, columns);
1854
sqlite3_free_table (data);
1858
static GSList *query_relation_ids (MusicSource *music_source, EntryType local_type,
1859
int local_id, int relation_apid) {
1860
Library *self = LIBRARY(music_source);
1861
EntryType foreign_type; int rows, columns;
1862
char **data = db_query_relations (self, local_type, local_id, relation_apid,
1863
&foreign_type, &rows, &columns);
1865
GSList *list = NULL;
1866
for (int i=0; i<rows; i++) {
1867
int pos = (i+1)*columns;
1868
list = g_slist_prepend (list, (void *)atoi(data[pos]));
1871
// FIXME: do we need to do this ? Or could sql return the results sorted
1872
// backwards if order matters ?? Would be quicker to return an array instead
1873
// of a list anyway.
1874
list = g_slist_reverse(list);
1818
sqlite3_free_table (data);
1875
sqlite3_free_table (data);
1819
return list;
1876
return list;
461
// Test that shadowed properties are correctly returned when entries are queried.
461
// Test that shadowed properties are correctly returned when entries are queried.
462
void test_shadowing() {
462
void test_shadowing_1 () {
463
MusicSource *source = source_constructor();
463
MusicSource *source = source_constructor();
465
music_source_begin_transaction (source);
465
music_source_begin_transaction (source);
565
_entry_cleanup();
565
_entry_cleanup();
568
568
void test_shadowing_2 () {
569
MusicSource *source = source_constructor();
571
music_source_begin_transaction (source);
572
test_add_song (source, 1, 1, 1, -1, -1);
573
test_add_song (source, 1, 2, 2, -1, -1);
574
music_source_end_transaction (source);
576
// There should be two recordings for artist 3. recording.artist is a shadowing property.
577
// FIXME: deprecate this function & use query_relations instead
578
GSList *list = music_source_query_entry_children_ids (source, 3, ENTRY_TYPE_RECORDING,
580
g_assert_cmpint (g_slist_length (list), ==, 2);
583
g_object_unref (source);
570
void test_relations() {
588
void test_relations() {
571
MusicSource *source = source_constructor();
589
MusicSource *source = source_constructor();
690
g_test_add_func(PATH_PRINTF("/%s/Pruning 1", root), test_pruning_1);
708
g_test_add_func(PATH_PRINTF("/%s/Pruning 1", root), test_pruning_1);
691
g_test_add_func(PATH_PRINTF("/%s/Pruning 2", root), test_pruning_2);
709
g_test_add_func(PATH_PRINTF("/%s/Pruning 2", root), test_pruning_2);
692
g_test_add_func(PATH_PRINTF("/%s/Pruning 3", root), test_pruning_3);
710
g_test_add_func(PATH_PRINTF("/%s/Pruning 3", root), test_pruning_3);
693
g_test_add_func(PATH_PRINTF("/%s/Shadowing", root), test_shadowing);
711
g_test_add_func(PATH_PRINTF("/%s/Shadowing 1", root), test_shadowing_1);
712
g_test_add_func(PATH_PRINTF("/%s/Shadowing 2", root), test_shadowing_2);
694
g_test_add_func(PATH_PRINTF("/%s/Relations", root), test_relations);
713
g_test_add_func(PATH_PRINTF("/%s/Relations", root), test_relations);
695
g_test_add_func(PATH_PRINTF("/%s/Recording Watch", root), test_recording_watch);
714
g_test_add_func(PATH_PRINTF("/%s/Recording Watch", root), test_recording_watch);
696
g_test_add_func(PATH_PRINTF("/%s/Album Watch 1", root), test_album_watch_1);
715
g_test_add_func(PATH_PRINTF("/%s/Album Watch 1", root), test_album_watch_1);
677
music_source_end_transaction(source);
677
music_source_end_transaction(source);
679
MusicSourceView *view;
679
MusicSourceView *view;
680
const char *reference_1[8][5] = {
680
/* const char *reference_1[8][5] = {
681
{"Test Artist 000000", "Test Composition 000001", "Test File 000000 (000001)", NULL, NULL},
681
{"Test Artist 000000", "Test Composition 000001", "Test File 000000 (000001)", NULL, NULL},
682
{"Test Artist 000000", "Test Composition 000000", "Test File 000001 (000000)", NULL, NULL},
682
{"Test Artist 000000", "Test Composition 000000", "Test File 000001 (000000)", NULL, NULL},
683
{"Test Artist 000000", "Test Composition 000000", "Test File 000002 (000000)", NULL, NULL},
683
{"Test Artist 000000", "Test Composition 000000", "Test File 000002 (000000)", NULL, NULL},
708
view = music_source_create_view(source, view_config_string_parse(config_2));
708
view = music_source_create_view(source, view_config_string_parse(config_2));
709
assert_view (view, NULL, 8, reference_2);
709
assert_view (view, NULL, 8, reference_2);
710
g_object_unref (view);
710
g_object_unref (view);
713
// 3 - Test for sort grouping being broken.
713
// 3 - Test for sort grouping being broken. Each pair of files is in a sort group defined by
714
// its track, in this case.
714
const char *reference_3[8][5] = {
715
const char *reference_3[8][5] = {
715
{"Test Artist 000000", "Test Composition 000000", "Test File 000001 (000000)", "Test Album 000001", "1"},
716
{"Test Artist 000000", "Test Composition 000000", "Test File 000001 (000000)", "Test Album 000001", "1"},
716
{"Test Artist 000000", "Test Composition 000000", "Test File 000002 (000000)", "Test Album 000001", "1"},
717
{"Test Artist 000000", "Test Composition 000000", "Test File 000002 (000000)", "Test Album 000001", "1"},
721
{"Test Artist 000001", "Test Composition 000003", "Test File 000004 (000003)", "Test Album 000002", "4"},
722
{"Test Artist 000001", "Test Composition 000003", "Test File 000004 (000003)", "Test Album 000002", "4"},
722
{"Test Artist 000001", "Test Composition 000003", "Test File 000007 (000003)", "Test Album 000002", "4"}};
723
{"Test Artist 000001", "Test Composition 000003", "Test File 000007 (000003)", "Test Album 000002", "4"}};
724
const char *config_3 = "artist[name]:album:release[date]:track[number]:recording:file[bitrate]";
725
const char *config_3 = "artist[name]:album:release[date]:track[number]:recording:file[path]";
725
view = music_source_create_view(source, view_config_string_parse(config_3));
726
view = music_source_create_view(source, view_config_string_parse(config_3));
726
assert_view (view, NULL, 8, reference_3);
727
assert_view (view, NULL, 8, reference_3);
727
g_object_unref (view);
728
g_object_unref (view);