1 /* This file is part of the KDE project
2 Copyright (C) 2004 Mark Kretschmann <markey@web.de>
3 Copyright (C) 2004 Christian Muehlhaeuser <chris@chris.de>
4 Copyright (C) 2004 Sami Nieminen <sami.nieminen@iki.fi>
5 Copyright (C) 2005 Ian Monroe <ian@monroe.nu>
6 Copyright (C) 2005 Jeff Mitchell <kde-dev@emailgoeshere.com>
7 Copyright (C) 2005 Isaiah Damron <xepo@trifault.net>
8 Copyright (C) 2005-2007 Alexandre Pereira de Oliveira <aleprj@gmail.com>
9 Copyright (C) 2006 Jonas Hurrelmann <j@outpo.st>
10 Copyright (C) 2006 Shane King <kde@dontletsstart.com>
11 Copyright (C) 2006 Peter C. Ndikuwera <pndiku@gmail.com>
13 This program is free software; you can redistribute it and/or
14 modify it under the terms of the GNU General Public License
15 as published by the Free Software Foundation; either version 2
16 of the License, or (at your option) any later version.
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
23 You should have received a copy of the GNU General Public License
24 along with this program; if not, write to the Free Software
25 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
28 #include "querybuilder.h"
30 #include "amarokconfig.h"
31 #include "collectiondb.h"
32 #include "expression.h"
33 #include "mountpointmanager.h"
37 //////////////////////////////////////////////////////////////////////////////////////////
39 //////////////////////////////////////////////////////////////////////////////////////////
41 QueryBuilder::QueryBuilder()
45 // there are a few string members with a large number of appends. to
46 // avoid reallocations, pre-reserve 1024 bytes and try never to assign
47 // it, instead doing setLength(0) and appends
48 // Note: unfortunately, QT3's setLength(), which is also called from append,
49 // squeezes the string if it's less than 4x the length. So this is useless.
50 // Uncomment after porting to QT4 if it's smarter about this, as the docs say.
51 // m_query.reserve(1024);
52 // m_values.reserve(1024);
53 // m_tables.reserve(1024);
58 QueryBuilder::linkTables( int tables
)
60 m_tables
.setLength(0);
62 m_tables
+= tableName( tabSong
);
64 if ( !(tables
& tabSong
) )
66 // check if only one table is selected (does somebody know a better way to check that?)
67 if (tables
== tabAlbum
|| tables
==tabArtist
|| tables
==tabGenre
|| tables
== tabYear
|| tables
== tabStats
|| tables
== tabPodcastEpisodes
|| tables
== tabPodcastFolders
|| tables
== tabPodcastChannels
|| tables
== tabLabels
) {
68 m_tables
.setLength( 0 );
69 m_tables
+= tableName(tables
);
75 if ( tables
& tabSong
)
77 if ( tables
& tabAlbum
)
78 ((m_tables
+= " LEFT JOIN ") += tableName( tabAlbum
)) += " ON album.id=tags.album";
79 if ( tables
& tabArtist
)
80 ((m_tables
+= " LEFT JOIN ") += tableName( tabArtist
)) += " ON artist.id=tags.artist";
81 if ( tables
& tabComposer
)
82 ((m_tables
+= " LEFT JOIN ") += tableName( tabComposer
)) += " ON composer.id=tags.composer";
83 if ( tables
& tabGenre
)
84 ((m_tables
+= " LEFT JOIN ") += tableName( tabGenre
)) += " ON genre.id=tags.genre";
85 if ( tables
& tabYear
)
86 ((m_tables
+= " LEFT JOIN ") += tableName( tabYear
)) += " ON year.id=tags.year";
87 if ( tables
& tabStats
)
89 ((m_tables
+= " LEFT JOIN ") += tableName( tabStats
))
90 += " ON statistics.url=tags.url AND statistics.deviceid = tags.deviceid";
91 //if ( !m_url.isEmpty() ) {
92 // QString url = QString( '.' ) + m_url;
93 // m_tables += QString( " OR statistics.deviceid = -1 AND statistics.url = '%1'" )
94 // .arg( CollectionDB::instance()->escapeString( url ) );
97 if ( tables
& tabLyrics
)
98 ((m_tables
+= " LEFT JOIN ") += tableName( tabLyrics
))
99 += " ON lyrics.url=tags.url AND lyrics.deviceid = tags.deviceid";
101 if ( tables
& tabDevices
)
102 ((m_tables
+= " LEFT JOIN ") += tableName( tabDevices
)) += " ON tags.deviceid = devices.id";
103 if ( tables
& tabLabels
)
104 ( m_tables
+= " LEFT JOIN tags_labels ON tags.url = tags_labels.url AND tags.deviceid = tags_labels.deviceid" )
105 += " LEFT JOIN labels ON tags_labels.labelid = labels.id";
111 QueryBuilder::addReturnValue( int table
, qint64 value
, bool caseSensitive
/* = false, unless value refers to a string */ )
113 caseSensitive
|= value
== valName
|| value
== valTitle
|| value
== valComment
;
115 if ( !m_values
.isEmpty() && m_values
!= "DISTINCT " ) m_values
+= ',';
117 if ( value
== valDummy
)
121 if ( caseSensitive
&& CollectionDB::instance()->getType() == DbConnection::mysql
)
122 m_values
+= "BINARY ";
123 m_values
+= tableName( table
) + '.';
124 m_values
+= valueName( value
);
127 m_linkTables
|= table
;
129 if ( value
& valURL
)
131 // make handling of deviceid transparent to calling code
132 m_deviceidPos
= m_returnValues
+ 1; //the return value after the url is the deviceid
134 m_values
+= tableName( table
);
136 m_values
+= valueName( valDeviceId
);
141 QueryBuilder::addReturnFunctionValue( int function
, int table
, qint64 value
)
143 // translate NULL and 0 values into the default value for percentage/rating
144 // First translate 0 to NULL via NULLIF, then NULL to default via COALESCE
145 bool defaults
= function
== funcAvg
&& ( value
& valScore
|| value
& valRating
);
147 if ( !m_values
.isEmpty() && m_values
!= "DISTINCT " ) m_values
+= ',';
148 m_values
+= functionName( function
) + '(';
150 m_values
+= "COALESCE(NULLIF(";
151 m_values
+= tableName( table
) + '.';
152 m_values
+= valueName( value
);
155 m_values
+= ", 0), ";
156 if ( value
& valScore
)
163 m_values
+= functionName( function
)+tableName( table
)+valueName( value
);
165 m_linkTables
|= table
;
166 if ( !m_showAll
) m_linkTables
|= tabSong
;
171 QueryBuilder::countReturnValues()
173 return m_returnValues
;
177 QueryBuilder::addUrlFilters( const QStringList
& filter
)
179 if ( !filter
.isEmpty() )
181 m_where
+= ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' ';
183 for ( uint i
= 0; i
< filter
.count(); i
++ )
185 int deviceid
= MountPointManager::instance()->getIdForUrl( filter
[i
] );
186 QString rpath
= MountPointManager::instance()->getRelativePath( deviceid
, filter
[i
] );
187 m_where
+= "OR (tags.url = '" + CollectionDB::instance()->escapeString( rpath
) + "' ";
188 m_where
+= QString( "AND tags.deviceid = %1 ) " ).arg( QString::number( deviceid
) );
189 //TODO MountPointManager fix this
195 m_linkTables
|= tabSong
;
199 QueryBuilder::setGoogleFilter( int defaultTables
, QString query
)
201 //TODO MountPointManager fix google syntax
202 //no clue about what needs to be done atm
203 ParsedExpression parsed
= ExpressionParser::parse( query
);
205 for( uint i
= 0, n
= parsed
.count(); i
< n
; ++i
) //check each part for matchiness
208 for( uint ii
= 0, nn
= parsed
[i
].count(); ii
< nn
; ++ii
)
210 const expression_element
&e
= parsed
[i
][ii
];
215 case expression_element::More
: mode
= modeGreater
; break;
216 case expression_element::Less
: mode
= modeLess
; break;
217 case expression_element::Contains
:
218 default: mode
= modeNormal
; break;
220 bool exact
= false; // enable for numeric values
224 if( e
.field
== "artist" )
226 else if( e
.field
== "composer" )
228 else if( e
.field
== "album" )
230 else if( e
.field
== "title" )
232 else if( e
.field
== "genre" )
234 else if( e
.field
== "year" )
240 else if( e
.field
== "score" )
246 else if( e
.field
== "rating" )
251 s
= QString::number( int( s
.toFloat() * 2 ) );
253 else if( e
.field
== "directory" )
256 value
= valDirectory
;
258 else if( e
.field
== "length" )
264 else if( e
.field
== "playcount" )
267 value
= valPlayCounter
;
270 else if( e
.field
== "samplerate" )
273 value
= valSamplerate
;
276 else if( e
.field
== "track" )
282 else if( e
.field
== "disc" || e
.field
== "discnumber" )
285 value
= valDiscNumber
;
288 else if( e
.field
== "size" || e
.field
== "filesize" )
293 if( s
.toLower().endsWith( "m" ) )
294 s
= QString::number( s
.left( s
.length()-1 ).toLong() * 1024 * 1024 );
295 else if( s
.toLower().endsWith( "k" ) )
296 s
= QString::number( s
.left( s
.length()-1 ).toLong() * 1024 );
298 else if( e
.field
== "filename" || e
.field
== "url" )
303 else if( e
.field
== "filetype" || e
.field
== "type" )
310 else if( e
.field
== "bitrate" )
316 else if( e
.field
== "comment" )
321 else if( e
.field
== "bpm" )
327 else if( e
.field
== "lyrics" )
332 else if( e
.field
== "device" )
335 value
= valDeviceLabel
;
337 else if( e
.field
== "mountpoint" )
340 value
= valMountPoint
;
342 else if( e
.field
== "label" )
351 excludeFilter( table
, value
, s
, mode
, exact
);
353 excludeFilter( table
>= 0 ? table
: defaultTables
, s
);
358 addFilter( table
, value
, s
, mode
, exact
);
360 addFilter( table
>= 0 ? table
: defaultTables
, s
);
368 QueryBuilder::addFilter( int tables
, const QString
& filter
)
370 if ( !filter
.isEmpty() )
372 m_where
+= ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' ';
374 if ( tables
& tabAlbum
)
375 m_where
+= "OR album.name " + CollectionDB::likeCondition( filter
, true, true );
376 if ( tables
& tabArtist
)
377 m_where
+= "OR artist.name " + CollectionDB::likeCondition( filter
, true, true );
378 if ( tables
& tabComposer
)
379 m_where
+= "OR composer.name " + CollectionDB::likeCondition( filter
, true, true );
380 if ( tables
& tabGenre
)
381 m_where
+= "OR genre.name " + CollectionDB::likeCondition( filter
, true, true );
382 if ( tables
& tabYear
)
383 m_where
+= "OR year.name " + CollectionDB::likeCondition( filter
, false, false );
384 if ( tables
& tabSong
)
385 m_where
+= "OR tags.title " + CollectionDB::likeCondition( filter
, true, true );
386 if ( tables
& tabLabels
)
387 m_where
+= "OR labels.name " + CollectionDB::likeCondition( filter
, true, true );
389 if ( i18n( "Unknown" ).contains( filter
, false ) )
391 if ( tables
& tabAlbum
)
392 m_where
+= "OR album.name = '' ";
393 if ( tables
& tabArtist
)
394 m_where
+= "OR artist.name = '' ";
395 if ( tables
& tabComposer
)
396 m_where
+= "OR composer.name = '' ";
397 if ( tables
& tabGenre
)
398 m_where
+= "OR genre.name = '' ";
399 if ( tables
& tabYear
)
400 m_where
+= "OR year.name = '' ";
401 if ( tables
& tabSong
)
402 m_where
+= "OR tags.title = '' ";
404 if ( ( tables
& tabArtist
) && i18n( "Various Artists" ).contains( filter
, false ) )
405 m_where
+= QString( "OR tags.sampler = %1 " ).arg( CollectionDB::instance()->boolT() );
409 m_linkTables
|= tables
;
413 QueryBuilder::addFilter( int tables
, qint64 value
, const QString
& filter
, int mode
, bool exact
)
415 //true for INTEGER fields (see comment of coalesceField(int, qint64)
416 bool useCoalesce
= coalesceField( tables
, value
);
417 m_where
+= ANDslashOR() + " ( ";
420 if (mode
== modeLess
|| mode
== modeGreater
)
422 QString escapedFilter
;
423 if (useCoalesce
&& DbConnection::sqlite
== CollectionDB::instance()->getDbConnectionType())
424 escapedFilter
= CollectionDB::instance()->escapeString( filter
);
426 escapedFilter
= '\'' + CollectionDB::instance()->escapeString( filter
) + "' ";
427 s
= ( mode
== modeLess
? "< " : "> " ) + escapedFilter
;
432 if (useCoalesce
&& DbConnection::sqlite
== CollectionDB::instance()->getDbConnectionType())
433 s
= " = " +CollectionDB::instance()->escapeString( filter
) + ' ';
435 s
= " = '" + CollectionDB::instance()->escapeString( filter
) + "' ";
437 s
= CollectionDB::likeCondition( filter
, mode
!= modeBeginMatch
, mode
!= modeEndMatch
);
440 if( coalesceField( tables
, value
) )
441 m_where
+= QString( "COALESCE(%1.%2,0) " ).arg( tableName( tables
) ).arg( valueName( value
) ) + s
;
443 m_where
+= QString( "%1.%2 " ).arg( tableName( tables
) ).arg( valueName( value
) ) + s
;
445 if ( !exact
&& (value
& valName
) && mode
== modeNormal
&& i18n( "Unknown").contains( filter
, false ) )
446 m_where
+= QString( "OR %1.%2 = '' " ).arg( tableName( tables
) ).arg( valueName( value
) );
450 m_linkTables
|= tables
;
454 QueryBuilder::addFilters( int tables
, const QStringList
& filter
)
456 if ( !filter
.isEmpty() )
458 m_where
+= ANDslashOR() + " ( " + CollectionDB::instance()->boolT() + ' ';
460 for ( uint i
= 0; i
< filter
.count(); i
++ )
462 m_where
+= ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' ';
464 if ( tables
& tabAlbum
)
465 m_where
+= "OR album.name " + CollectionDB::likeCondition( filter
[i
], true, true );
466 if ( tables
& tabArtist
)
467 m_where
+= "OR artist.name " + CollectionDB::likeCondition( filter
[i
], true, true );
468 if ( tables
& tabComposer
)
469 m_where
+= "OR composer.name " + CollectionDB::likeCondition( filter
[i
], true, true );
470 if ( tables
& tabGenre
)
471 m_where
+= "OR genre.name " + CollectionDB::likeCondition( filter
[i
], true, true );
472 if ( tables
& tabYear
)
473 m_where
+= "OR year.name " + CollectionDB::likeCondition( filter
[i
], false, false );
474 if ( tables
& tabSong
)
475 m_where
+= "OR tags.title " + CollectionDB::likeCondition( filter
[i
], true, true );
476 if ( tables
& tabLabels
)
477 m_where
+= "OR labels.name " + CollectionDB::likeCondition( filter
[i
], true, true );
479 if ( i18n( "Unknown" ).contains( filter
[i
], false ) )
481 if ( tables
& tabAlbum
)
482 m_where
+= "OR album.name = '' ";
483 if ( tables
& tabArtist
)
484 m_where
+= "OR artist.name = '' ";
485 if ( tables
& tabComposer
)
486 m_where
+= "OR composer.name = '' ";
487 if ( tables
& tabGenre
)
488 m_where
+= "OR genre.name = '' ";
489 if ( tables
& tabYear
)
490 m_where
+= "OR year.name = '' ";
491 if ( tables
& tabSong
)
492 m_where
+= "OR tags.title = '' ";
494 if ( i18n( "Various Artists" ).contains( filter
[ i
], false ) && ( tables
& tabArtist
) )
495 m_where
+= "OR tags.sampler = " + CollectionDB::instance()->boolT() + ' ';
502 m_linkTables
|= tables
;
506 QueryBuilder::excludeFilter( int tables
, const QString
& filter
)
508 if ( !filter
.isEmpty() )
510 m_where
+= ANDslashOR() + " ( " + CollectionDB::instance()->boolT() + ' ';
513 if ( tables
& tabAlbum
)
514 m_where
+= "AND album.name NOT " + CollectionDB::likeCondition( filter
, true, true );
515 if ( tables
& tabArtist
)
516 m_where
+= "AND artist.name NOT " + CollectionDB::likeCondition( filter
, true, true );
517 if ( tables
& tabComposer
)
518 m_where
+= "AND composer.name NOT " + CollectionDB::likeCondition( filter
, true, true );
519 if ( tables
& tabGenre
)
520 m_where
+= "AND genre.name NOT " + CollectionDB::likeCondition( filter
, true, true );
521 if ( tables
& tabYear
)
522 m_where
+= "AND year.name NOT " + CollectionDB::likeCondition( filter
, false, false );
523 if ( tables
& tabSong
)
524 m_where
+= "AND tags.title NOT " + CollectionDB::likeCondition( filter
, true, true );
525 if ( tables
& tabLabels
)
526 m_where
+= "AND labels.name NOT " + CollectionDB::likeCondition( filter
, true, true );
528 if ( i18n( "Unknown" ).contains( filter
, false ) )
530 if ( tables
& tabAlbum
)
531 m_where
+= "AND album.name <> '' ";
532 if ( tables
& tabArtist
)
533 m_where
+= "AND artist.name <> '' ";
534 if ( tables
& tabComposer
)
535 m_where
+= "AND composer.name <> '' ";
536 if ( tables
& tabGenre
)
537 m_where
+= "AND genre.name <> '' ";
538 if ( tables
& tabYear
)
539 m_where
+= "AND year.name <> '' ";
540 if ( tables
& tabSong
)
541 m_where
+= "AND tags.title <> '' ";
544 if ( i18n( "Various Artists" ).contains( filter
, false ) && ( tables
& tabArtist
) )
545 m_where
+= "AND tags.sampler = " + CollectionDB::instance()->boolF() + ' ';
551 m_linkTables
|= tables
;
555 QueryBuilder::excludeFilter( int tables
, qint64 value
, const QString
& filter
, int mode
, bool exact
)
557 m_where
+= ANDslashOR() + " ( ";
560 if (mode
== modeLess
|| mode
== modeGreater
)
561 s
= ( mode
== modeLess
? ">= '" : "<= '" ) + CollectionDB::instance()->escapeString( filter
) + "' ";
567 filter
.toInt( &isNumber
);
569 s
= " <> " + CollectionDB::instance()->escapeString( filter
) + ' ';
571 s
= " <> '" + CollectionDB::instance()->escapeString( filter
) + "' ";
574 s
= "NOT " + CollectionDB::instance()->likeCondition( filter
, mode
!= modeBeginMatch
, mode
!= modeEndMatch
) + ' ';
577 if( coalesceField( tables
, value
) )
578 m_where
+= QString( "COALESCE(%1.%2,0) " ).arg( tableName( tables
) ).arg( valueName( value
) ) + s
;
580 m_where
+= QString( "%1.%2 " ).arg( tableName( tables
) ).arg( valueName( value
) ) + s
;
582 if ( !exact
&& (value
& valName
) && mode
== modeNormal
&& i18n( "Unknown").contains( filter
, false ) )
583 m_where
+= QString( "AND %1.%2 <> '' " ).arg( tableName( tables
) ).arg( valueName( value
) );
587 m_linkTables
|= tables
;
591 QueryBuilder::addMatch( int tables
, const QString
& match
, bool interpretUnknown
/* = true */, bool caseSensitive
/* = true */ )
593 QString matchCondition
= caseSensitive
? CollectionDB::exactCondition( match
) : CollectionDB::likeCondition( match
);
595 (((m_where
+= ANDslashOR()) += " ( ") += CollectionDB::instance()->boolF()) += ' ';
596 if ( tables
& tabAlbum
)
597 (m_where
+= "OR album.name ") += matchCondition
;
598 if ( tables
& tabArtist
)
599 (m_where
+= "OR artist.name ") += matchCondition
;
600 if ( tables
& tabComposer
)
601 (m_where
+= "OR composer.name ") += matchCondition
;
602 if ( tables
& tabGenre
)
603 (m_where
+= "OR genre.name ") += matchCondition
;
604 if ( tables
& tabYear
)
605 (m_where
+= "OR year.name ") += matchCondition
;
606 if ( tables
& tabSong
)
607 (m_where
+= "OR tags.title ") += matchCondition
;
608 if ( tables
& tabLabels
)
609 (m_where
+= "OR labels.name ") += matchCondition
;
611 static QString i18nUnknown
= i18n("Unknown");
613 if ( interpretUnknown
&& match
== i18nUnknown
)
615 if ( tables
& tabAlbum
) m_where
+= "OR album.name = '' ";
616 if ( tables
& tabArtist
) m_where
+= "OR artist.name = '' ";
617 if ( tables
& tabComposer
) m_where
+= "OR composer.name = '' ";
618 if ( tables
& tabGenre
) m_where
+= "OR genre.name = '' ";
619 if ( tables
& tabYear
) m_where
+= "OR year.name = '' ";
621 if ( tables
& tabLabels
&& match
.isEmpty() )
622 m_where
+= " OR labels.name IS NULL ";
625 m_linkTables
|= tables
;
630 QueryBuilder::addMatch( int tables
, qint64 value
, const QString
& match
, bool interpretUnknown
/* = true */, bool caseSensitive
/* = true */ )
632 m_where
+= ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' ';
633 if ( value
& valURL
)
635 //FIXME max: doesn't work yet if we are querying for the mount point part of a directory
636 if ( value
& valURL
|| value
& valDirectory
)
638 int deviceid
= MountPointManager::instance()->getIdForUrl( match
);
639 QString rpath
= MountPointManager::instance()->getRelativePath( deviceid
, match
);
640 //we are querying for a specific path, so we don't need the tags.deviceid IN (...) stuff
641 //which is automatially appended if m_showAll = false
643 m_where
+= QString( "OR %1.%2 " )
644 .arg( tableName( tables
) )
645 .arg( valueName( value
) );
646 m_where
+= caseSensitive
? CollectionDB::exactCondition( rpath
) : CollectionDB::likeCondition( rpath
);
647 m_where
+= QString( " AND %1.deviceid = %2 " ).arg( tableName( tables
) ).arg( deviceid
);
648 if ( deviceid
!= -1 )
651 QString
rpath2( '.' + match
);
652 m_where
+= QString( " OR %1.%2 " ).arg( tableName( tables
) ).arg( valueName( value
) );
653 m_where
+= caseSensitive
? CollectionDB::exactCondition( rpath2
) : CollectionDB::likeCondition( rpath2
);
654 m_where
+= QString( " AND %1.deviceid = -1 " ).arg( tableName( tables
) );
659 m_where
+= QString( "OR %1.%2 " ).arg( tableName( tables
) ).arg( valueName( value
) );
660 m_where
+= caseSensitive
? CollectionDB::exactCondition( match
) : CollectionDB::likeCondition( match
);
663 if ( ( value
& valName
) && interpretUnknown
&& match
== i18n( "Unknown" ) )
664 m_where
+= QString( "OR %1.%2 = '' " ).arg( tableName( tables
) ).arg( valueName( value
) );
668 m_linkTables
|= tables
;
673 QueryBuilder::addMatches( int tables
, const QStringList
& match
, bool interpretUnknown
/* = true */, bool caseSensitive
/* = true */ )
675 QStringList matchConditions
;
676 for ( uint i
= 0; i
< match
.count(); i
++ )
677 matchConditions
<< ( caseSensitive
? CollectionDB::exactCondition( match
[i
] ) : CollectionDB::likeCondition( match
[i
] ) );
679 m_where
+= ANDslashOR() + " ( " + CollectionDB::instance()->boolF() + ' ';
681 for ( uint i
= 0; i
< match
.count(); i
++ )
683 if ( tables
& tabAlbum
)
684 m_where
+= "OR album.name " + matchConditions
[ i
];
685 if ( tables
& tabArtist
)
686 m_where
+= "OR artist.name " + matchConditions
[ i
];
687 if ( tables
& tabComposer
)
688 m_where
+= "OR composer.name " + matchConditions
[ i
];
689 if ( tables
& tabGenre
)
690 m_where
+= "OR genre.name " + matchConditions
[ i
];
691 if ( tables
& tabYear
)
692 m_where
+= "OR year.name " + matchConditions
[ i
];
693 if ( tables
& tabSong
)
694 m_where
+= "OR tags.title " + matchConditions
[ i
];
695 if ( tables
& tabStats
)
696 m_where
+= "OR statistics.url " + matchConditions
[ i
];
697 if ( tables
& tabLabels
)
698 (m_where
+= "OR labels.name ") += matchConditions
[ i
];
701 if ( interpretUnknown
&& match
[i
] == i18n( "Unknown" ) )
703 if ( tables
& tabAlbum
) m_where
+= "OR album.name = '' ";
704 if ( tables
& tabArtist
) m_where
+= "OR artist.name = '' ";
705 if ( tables
& tabComposer
) m_where
+= "OR composer.name = '' ";
706 if ( tables
& tabGenre
) m_where
+= "OR genre.name = '' ";
707 if ( tables
& tabYear
) m_where
+= "OR year.name = '' ";
709 if ( tables
& tabLabels
&& match
[i
].isEmpty() )
710 m_where
+= " OR labels.name IS NULL ";
714 m_linkTables
|= tables
;
718 QueryBuilder::excludeMatch( int tables
, const QString
& match
)
720 m_where
+= ANDslashOR() + " ( " + CollectionDB::instance()->boolT() + ' ';
721 if ( tables
& tabAlbum
) m_where
+= "AND album.name <> '" + CollectionDB::instance()->escapeString( match
) + "' ";
722 if ( tables
& tabArtist
) m_where
+= "AND artist.name <> '" + CollectionDB::instance()->escapeString( match
) + "' ";
723 if ( tables
& tabComposer
) m_where
+= "AND composer.name <> '" + CollectionDB::instance()->escapeString( match
) + "' ";
724 if ( tables
& tabGenre
) m_where
+= "AND genre.name <> '" + CollectionDB::instance()->escapeString( match
) + "' ";
725 if ( tables
& tabYear
) m_where
+= "AND year.name <> '" + CollectionDB::instance()->escapeString( match
) + "' ";
726 if ( tables
& tabSong
) m_where
+= "AND tags.title <> '" + CollectionDB::instance()->escapeString( match
) + "' ";
727 if ( tables
& tabLabels
) m_where
+= "AND labels.name <> '" + CollectionDB::instance()->escapeString( match
) + "' ";
729 if ( match
== i18n( "Unknown" ) )
731 if ( tables
& tabAlbum
) m_where
+= "AND album.name <> '' ";
732 if ( tables
& tabArtist
) m_where
+= "AND artist.name <> '' ";
733 if ( tables
& tabComposer
) m_where
+= "AND composer.name <> '' ";
734 if ( tables
& tabGenre
) m_where
+= "AND genre.name <> '' ";
735 if ( tables
& tabYear
) m_where
+= "AND year.name <> '' ";
739 m_linkTables
|= tables
;
744 QueryBuilder::exclusiveFilter( int tableMatching
, int tableNotMatching
, qint64 value
)
747 m_where
+= tableName( tableNotMatching
) + '.';
748 m_where
+= valueName( value
);
749 m_where
+= " IS null ";
751 m_linkTables
|= tableMatching
;
752 m_linkTables
|= tableNotMatching
;
757 QueryBuilder::addNumericFilter(int tables
, qint64 value
, const QString
&n
,
758 int mode
/* = modeNormal */,
759 const QString
&endRange
/* = QString::null */ )
761 m_where
.append( ANDslashOR() ).append( " ( " );
763 if ( coalesceField( tables
, value
) )
764 m_where
.append("COALESCE(");
766 m_where
.append( tableName( tables
) ).append( '.' ).append( valueName( value
) );
768 if ( coalesceField( tables
, value
) )
769 m_where
.append(",0)");
773 m_where
.append( " = " ); break;
775 m_where
.append( " < " ); break;
777 m_where
.append( " > " ); break;
779 m_where
.append( " BETWEEN " ); break;
781 m_where
.append(" NOT BETWEEN "); break;
783 qWarning( "Unhandled mode in addNumericFilter, using equals: %d", mode
);
784 m_where
.append( " = " );
788 if ( mode
== modeBetween
|| mode
== modeNotBetween
)
789 m_where
.append( " AND " ).append( endRange
);
791 m_where
.append( " ) " );
792 m_linkTables
|= tables
;
798 QueryBuilder::setOptions( int options
)
800 if ( options
& optNoCompilations
|| options
& optOnlyCompilations
)
801 m_linkTables
|= tabSong
;
803 if ( options
& optNoCompilations
) m_where
+= QString("AND tags.sampler = %1 ").arg(CollectionDB::instance()->boolF());
804 if ( options
& optOnlyCompilations
) m_where
+= QString("AND tags.sampler = %1 ").arg(CollectionDB::instance()->boolT());
806 if (CollectionDB::instance()->getType() == DbConnection::postgresql
&& options
& optRemoveDuplicates
&& options
& optRandomize
)
808 m_values
= "DISTINCT " + CollectionDB::instance()->randomFunc() + " AS __random "+ m_values
;
809 if ( !m_sort
.isEmpty() )
811 m_sort
+= CollectionDB::instance()->randomFunc() + ' ';
815 if ( options
& optRemoveDuplicates
)
816 m_values
= "DISTINCT " + m_values
;
817 if ( options
& optRandomize
)
819 if ( !m_sort
.isEmpty() ) m_sort
+= ',';
820 m_sort
+= CollectionDB::instance()->randomFunc() + ' ';
824 if ( options
& optShowAll
) m_showAll
= true;
829 QueryBuilder::sortBy( int table
, qint64 value
, bool descending
)
831 //shall we sort case-sensitively? (not for integer columns!)
833 if ( value
& valID
|| value
& valTrack
|| value
& valScore
|| value
& valRating
|| value
& valLength
|| value
& valBitrate
||
834 value
& valSamplerate
|| value
& valPlayCounter
|| value
& valAccessDate
|| value
& valCreateDate
||
835 value
& valFilesize
|| value
& valDiscNumber
||
839 // only coalesce for certain columns
841 if ( value
& valScore
|| value
& valRating
|| value
& valPlayCounter
|| value
& valAccessDate
|| value
& valCreateDate
)
844 if ( !m_sort
.isEmpty() ) m_sort
+= ',';
845 if ( b
) m_sort
+= "LOWER( ";
846 if ( c
) m_sort
+= "COALESCE( ";
848 m_sort
+= tableName( table
) + '.';
849 m_sort
+= valueName( value
);
851 if ( c
) m_sort
+= ", 0 )";
853 if ( b
) m_sort
+= " ) ";
854 if ( descending
) m_sort
+= " DESC ";
856 if (CollectionDB::instance()->getType() == DbConnection::postgresql
)
858 if (!m_values
.isEmpty()) m_values
+= ',';
859 if ( b
) m_values
+= "LOWER( ";
860 m_values
+= tableName( table
) + '.';
861 m_values
+= valueName( value
);
862 if ( b
) m_values
+= ')';
863 m_values
+= " as __discard ";
866 m_linkTables
|= table
;
870 QueryBuilder::sortByFunction( int function
, int table
, qint64 value
, bool descending
)
872 // This function should be used with the equivalent addReturnFunctionValue (with the same function on same values)
873 // since it uses the "func(table.value) AS functablevalue" definition.
875 // this column is already coalesced, but need to reconstruct for postgres
876 bool defaults
= function
== funcAvg
&& ( value
& valScore
|| value
& valRating
);
878 //shall we sort case-sensitively? (not for integer columns!)
880 if ( value
& valID
|| value
& valTrack
|| value
& valScore
|| value
& valRating
|| value
& valLength
|| value
& valBitrate
||
881 value
& valSamplerate
|| value
& valPlayCounter
|| value
& valAccessDate
|| value
& valCreateDate
||
882 value
& valFilesize
|| value
& valDiscNumber
||
886 // only coalesce for certain columns
888 if ( !defaults
&& ( value
& valScore
|| value
& valRating
|| value
& valPlayCounter
|| value
& valAccessDate
|| value
& valCreateDate
) )
891 if ( !m_sort
.isEmpty() ) m_sort
+= ',';
892 //m_sort += functionName( function ) + '(';
893 if ( b
) m_sort
+= "LOWER( ";
894 if ( c
&& CollectionDB::instance()->getType() != DbConnection::mysql
) m_sort
+= "COALESCE( ";
898 if (CollectionDB::instance()->getType() == DbConnection::postgresql
)
900 columnName
= functionName( function
) + '(';
902 columnName
+= "COALESCE(NULLIF(";
903 columnName
+= tableName( table
)+'.'+valueName( value
);
906 columnName
+= ", 0), ";
907 if ( value
& valScore
)
916 columnName
= functionName( function
)+tableName( table
)+valueName( value
);
918 m_sort
+= columnName
;
920 if ( c
&& CollectionDB::instance()->getType() != DbConnection::mysql
) m_sort
+= ", 0 )";
922 if ( b
) m_sort
+= " ) ";
924 if ( descending
) m_sort
+= " DESC ";
926 m_linkTables
|= table
;
930 QueryBuilder::groupBy( int table
, qint64 value
)
932 if ( !m_group
.isEmpty() ) m_group
+= ',';
934 //Do case-sensitive comparisons for MySQL too. See also QueryBuilder::addReturnValue
935 if ( DbConnection::mysql
== CollectionDB::instance()->getDbConnectionType() &&
936 ( value
== valName
|| value
== valTitle
|| value
== valComment
) )
938 m_group
+= "BINARY ";
941 m_group
+= tableName( table
) + '.';
942 m_group
+= valueName( value
);
944 m_linkTables
|= table
;
948 QueryBuilder::having( int table
, qint64 value
, int function
, int mode
, const QString
& match
)
950 if( !m_having
.isEmpty() ) m_having
+= " AND ";
952 QString fn
= functionName( function
);
954 m_having
+= tableName( table
) + '.' + valueName( value
) :
955 m_having
+= functionName( function
)+'('+tableName( table
)+'.'+valueName( value
)+')';
960 m_having
+= '=' + match
;
964 m_having
+= '<' + match
;
968 m_having
+= '>' + match
;
976 QueryBuilder::setLimit( int startPos
, int length
)
978 m_limit
= QString( " LIMIT %2 OFFSET %1 " ).arg( startPos
).arg( length
);
982 QueryBuilder::shuffle( int table
, qint64 value
)
984 if ( !m_sort
.isEmpty() ) m_sort
+= " , ";
985 if ( table
== 0 || value
== 0 ) {
987 m_sort
+= CollectionDB::instance()->randomFunc();
989 // This is the score weighted random order.
991 // The RAND() function returns random values equally distributed between 0.0
992 // (inclusive) and 1.0 (exclusive). The obvious way to get this order is to
993 // put every track <score> times into a list, sort the list by RAND()
994 // (i.e. shuffle it) and discard every occurrence of every track but the very
995 // first of each. By putting every track into the list only once but applying
996 // a transfer function T_s(x) := 1-(1-x)^(1/s) where s is the score, to RAND()
997 // before sorting the list, exactly the same distribution of tracks can be
998 // achieved (for a proof write to Stefan Siegel <kde@sdas.de>)
1000 // In the query below a simplified function is used: The score is incremented
1001 // by one to prevent division by zero, RAND() is used instead of 1-RAND()
1002 // because it doesn't matter if it becomes zero (the exponent is always
1003 // non-zero), and finally POWER(...) is used instead of 1-POWER(...) because it
1004 // only changes the order type.
1005 m_sort
+= QString("POWER( %1, 1.0 / (%2.%3 + 1) ) DESC")
1006 .arg( CollectionDB::instance()->randomFunc() )
1007 .arg( tableName( table
) )
1008 .arg( valueName( value
) );
1010 m_linkTables
|= table
;
1015 /* NOTE: It's important to keep these two functions and the const in sync! */
1016 /* NOTE: It's just as important to keep tags.url first! */
1018 QueryBuilder::dragFieldCount
= 21;
1021 QueryBuilder::dragSQLFields()
1023 return "tags.url, tags.deviceid, album.name, artist.name, composer.name, "
1024 "genre.name, tags.title, year.name, "
1025 "tags.comment, tags.track, tags.bitrate, tags.discnumber, "
1026 "tags.length, tags.samplerate, tags.filesize, "
1027 "tags.sampler, tags.filetype, tags.bpm, "
1028 "statistics.percentage, statistics.rating, statistics.playcounter, "
1029 "statistics.accessdate";
1033 QueryBuilder::initSQLDrag()
1036 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valURL
);
1037 addReturnValue( QueryBuilder::tabAlbum
, QueryBuilder::valName
);
1038 addReturnValue( QueryBuilder::tabArtist
, QueryBuilder::valName
);
1039 addReturnValue( QueryBuilder::tabComposer
, QueryBuilder::valName
);
1040 addReturnValue( QueryBuilder::tabGenre
, QueryBuilder::valName
);
1041 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valTitle
);
1042 addReturnValue( QueryBuilder::tabYear
, QueryBuilder::valName
);
1043 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valComment
);
1044 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valTrack
);
1045 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valBitrate
);
1046 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valDiscNumber
);
1047 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valLength
);
1048 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valSamplerate
);
1049 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valFilesize
);
1050 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valIsCompilation
);
1051 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valFileType
);
1052 addReturnValue( QueryBuilder::tabSong
, QueryBuilder::valBPM
);
1053 addReturnValue( QueryBuilder::tabStats
, QueryBuilder::valScore
);
1054 addReturnValue( QueryBuilder::tabStats
, QueryBuilder::valRating
);
1055 addReturnValue( QueryBuilder::tabStats
, QueryBuilder::valPlayCounter
);
1056 addReturnValue( QueryBuilder::tabStats
, QueryBuilder::valAccessDate
);
1061 QueryBuilder::buildQuery( bool withDeviceidPlaceholder
)
1063 if ( m_query
.isEmpty() )
1065 linkTables( m_linkTables
);
1066 m_query
+= "SELECT ";
1067 m_query
+= m_values
;
1068 m_query
+= " FROM ";
1069 m_query
+= m_tables
;
1072 m_query
+= " WHERE ";
1073 m_query
+= CollectionDB::instance()->boolT();
1076 if ( !m_showAll
&& ( m_linkTables
& tabSong
|| m_tables
.contains( tableName( tabSong
) ) ) ) //Only stuff on mounted devices, unless you use optShowAll
1078 if ( withDeviceidPlaceholder
)
1079 m_query
+= "(*MountedDeviceSelection*)";
1082 IdList list
= MountPointManager::instance()->getMountedDeviceIds();
1083 //debug() << "number of device ids " << list.count();
1084 m_query
+= " AND tags.deviceid IN (";
1085 oldForeachType( IdList
, list
)
1087 if ( it
!= list
.begin() ) m_query
+= ',';
1088 m_query
+= QString::number( *it
);
1093 // GROUP BY must be before ORDER BY for sqlite
1094 // HAVING must be between GROUP BY and ORDER BY
1095 if ( !m_group
.isEmpty() ) { m_query
+= " GROUP BY "; m_query
+= m_group
; }
1096 if ( !m_having
.isEmpty() ) { m_query
+= " HAVING "; m_query
+= m_having
; }
1097 if ( !m_sort
.isEmpty() ) { m_query
+= " ORDER BY "; m_query
+= m_sort
; }
1103 // get the builded SQL-Query (used in smartplaylisteditor soon)
1105 QueryBuilder::getQuery()
1107 if ( m_query
.isEmpty())
1118 //debug() << m_query;
1119 QStringList rs
= CollectionDB::instance()->query( m_query
);
1120 //calling code is unaware of the dynamic collection implementation, it simply expects an URL
1121 if( m_deviceidPos
> 0 )
1123 return cleanURL( rs
);
1131 QueryBuilder::clear()
1133 m_query
.setLength(0);
1134 m_values
.setLength(0);
1135 m_tables
.setLength(0);
1136 m_join
.setLength(0);
1137 m_where
.setLength(0);
1138 m_sort
.setLength(0);
1139 m_group
.setLength(0);
1140 m_limit
.setLength(0);
1141 m_having
.setLength(0);
1152 QueryBuilder::valForFavoriteSorting() {
1153 qint64 favSortBy
= valRating
;
1154 if ( !AmarokConfig::useScores() && !AmarokConfig::useRatings() )
1155 favSortBy
= valPlayCounter
;
1156 else if( !AmarokConfig::useRatings() )
1157 favSortBy
= valScore
;
1162 QueryBuilder::sortByFavorite() {
1163 if ( AmarokConfig::useRatings() )
1164 sortBy(tabStats
, valRating
, true );
1165 if ( AmarokConfig::useScores() )
1166 sortBy(tabStats
, valScore
, true );
1167 sortBy(tabStats
, valPlayCounter
, true );
1172 QueryBuilder::sortByFavoriteAvg() {
1173 // Due to MySQL4 weirdness, we need to add the function we're using to sort
1174 // as return values as well.
1175 if ( AmarokConfig::useRatings() ) {
1176 sortByFunction(funcAvg
, tabStats
, valRating
, true );
1177 addReturnFunctionValue( funcAvg
, tabStats
, valRating
);
1179 if ( AmarokConfig::useScores() ) {
1180 sortByFunction(funcAvg
, tabStats
, valScore
, true );
1181 addReturnFunctionValue( funcAvg
, tabStats
, valScore
);
1183 sortByFunction(funcAvg
, tabStats
, valPlayCounter
, true );
1184 addReturnFunctionValue( funcAvg
, tabStats
, valPlayCounter
);
1186 //exclude unrated and unplayed
1187 if( !m_having
.isEmpty() )
1188 m_having
+= " AND ";
1190 if (AmarokConfig::useRatings() )
1191 m_having
+= QString("%1(%2.%3) > 0 OR ")
1192 .arg( functionName( funcAvg
), tableName(tabStats
), valueName(valRating
) );
1193 m_having
+= QString("%1(%2.%3) > 0")
1194 .arg( functionName( funcAvg
), tableName(tabStats
), valueName(valPlayCounter
) );
1198 // Helper method -- given a value, returns the index of the bit that is
1199 // set, if only one, otherwise returns -1
1200 // Binsearch seems appropriate since the values enum has 40 members
1201 template<class ValueType
>
1203 searchBit( ValueType value
, int numBits
) {
1204 int low
= 0, high
= numBits
- 1;
1205 while( low
<= high
) {
1206 int mid
= (low
+ high
) / 2;
1207 ValueType compare
= static_cast<ValueType
>( 1 ) << mid
;
1208 if ( value
== compare
) return mid
;
1209 else if ( value
< compare
) high
= mid
- 1;
1217 QueryBuilder::tableName( int table
)
1219 // optimize for 1 table which is by far the most frequent case
1220 static const QString tabNames
[] = {
1226 "<unused>", // 32 is missing from the enum
1237 int oneBit
= searchBit( table
, sizeof( tabNames
) / sizeof( QString
) );
1238 if ( oneBit
>= 0 ) return tabNames
[oneBit
];
1240 // slow path: multiple tables. This seems to be unneeded at the moment,
1241 // but leaving it here since it appears to be intended usage
1244 if ( CollectionDB::instance()->getType() != DbConnection::postgresql
)
1246 if ( table
& tabSong
) tables
+= ",tags";
1248 if ( table
& tabArtist
) tables
+= ",artist";
1249 if ( table
& tabComposer
) tables
+= ",composer";
1250 if ( table
& tabAlbum
) tables
+= ",album";
1251 if ( table
& tabGenre
) tables
+= ",genre";
1252 if ( table
& tabYear
) tables
+= ",year";
1253 if ( table
& tabStats
) tables
+= ",statistics";
1254 if ( table
& tabLyrics
) tables
+= ",lyrics";
1255 if ( table
& tabPodcastChannels
) tables
+= ",podcastchannels";
1256 if ( table
& tabPodcastEpisodes
) tables
+= ",podcastepisodes";
1257 if ( table
& tabPodcastFolders
) tables
+= ",podcasttables";
1258 if ( CollectionDB::instance()->getType() == DbConnection::postgresql
)
1260 if ( table
& tabSong
) tables
+= ",tags";
1263 if ( table
& tabDevices
) tables
+= ",devices";
1264 if ( table
& tabLabels
) tables
+= ",labels";
1265 // when there are multiple tables involved, we always need table tags for linking them
1266 return tables
.mid( 1 );
1271 QueryBuilder::valueName( qint64 value
)
1273 static const QString values
[] = {
1317 int oneBit
= searchBit( value
, sizeof( values
) / sizeof( QString
) );
1318 if ( oneBit
>= 0 ) return values
[oneBit
];
1320 static const QString
error( "<ERROR valueName>" );
1325 * Return true if we should call COALESCE(..,0) for this DB field
1326 * (field names sourced from the old smartplaylistbrowser.cpp code)
1327 * Warning: addFilter( int, qint64, const QString&, int bool )
1328 * expects this method to return true for all statistics table clomuns of type INTEGER
1329 * Sqlite doesn't like comparing strings to an INTEGER column.
1332 QueryBuilder::coalesceField( int table
, qint64 value
)
1334 if( tableName( table
) == "statistics" &&
1335 ( valueName( value
) == "playcounter" ||
1336 valueName( value
) == "rating" ||
1337 valueName( value
) == "percentage" ||
1338 valueName( value
) == "accessdate" ||
1339 valueName( value
) == "createdate"
1347 QueryBuilder::functionName( int function
)
1351 if ( function
& funcCount
) functions
+= "Count";
1352 if ( function
& funcMax
) functions
+= "Max";
1353 if ( function
& funcMin
) functions
+= "Min";
1354 if ( function
& funcAvg
) functions
+= "Avg";
1355 if ( function
& funcSum
) functions
+= "Sum";
1360 // FIXME: the two functions below are inefficient, but this patch is getting too
1361 // big already. They are not on any critical path right now. Ovy
1363 QueryBuilder::getTableByName(const QString
&name
)
1365 for ( int i
= 1; i
<= tabLabels
; i
<<= 1 )
1367 if (tableName(i
) == name
) return i
;
1373 QueryBuilder::getValueByName(const QString
&name
)
1375 for ( qint64 i
= 1; i
<= valType
; i
<<= 1 ) {
1376 if (valueName(i
) == name
) return i
;
1383 QueryBuilder::getField(const QString
&tableValue
, int *table
, qint64
*value
)
1385 int dotIndex
= tableValue
.find( '.' ) ;
1386 if ( dotIndex
< 0 ) return false;
1387 int tmpTable
= getTableByName( tableValue
.left(dotIndex
) );
1388 quint64 tmpValue
= getValueByName( tableValue
.mid( dotIndex
+ 1 ) );
1389 if ( tmpTable
>= 0 && value
) {
1396 qFatal("invalid table.value: %s", qPrintable(tableValue
));
1404 QueryBuilder::cleanURL( QStringList result
)
1406 //this method replaces the fields for relative path and devive/media id with a
1407 //single field containing the absolute path for each row
1409 for( QMutableStringListIterator
iter( result
); iter
.hasNext(); )
1412 if ( (count
% (m_returnValues
+ 1)) + 1== m_deviceidPos
)
1414 //this block is reached when the iterator points at the relative path
1416 QString rpath
= iter
.next();
1417 int deviceid
= iter
.peekNext().toInt();
1418 QString abspath
= MountPointManager::instance()->getAbsolutePath( deviceid
, rpath
);
1419 iter
.setValue( abspath
);
1422 //we advanced the iterator over two fields in this iteration