-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Crate filter #1263
Crate filter #1263
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm glad we're off to an early start! It looks pretty good so far. I have tested it and it works. Combining multiple crate filters and negation work too, which opens up interesting new possibilities for library organization.
src/library/searchquery.cpp
Outdated
bool CrateFilterNode::match(const TrackPointer& pTrack) const { | ||
QString id = pTrack->getId().toString(); | ||
for (const QString& it : m_trackIds) { | ||
if (it == id) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String operations are expensive and should be minimized. Instead, store a list of TrackIds from the query, then you can simply check if pTrack->getId() is in that list. Do the conversion to QStrings only where needed in the toSql
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can use contains(const QString & str, Qt::CaseSensitivity cs = Qt::CaseSensitive)
src/library/basetrackcache.cpp
Outdated
@@ -14,7 +14,7 @@ | |||
|
|||
namespace { | |||
|
|||
const bool sDebug = false; | |||
const bool sDebug = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will need to be reverted before merging.
src/library/searchquery.cpp
Outdated
@@ -7,6 +7,12 @@ | |||
#include "library/dao/trackschema.h" | |||
#include "util/db/sqllikewildcards.h" | |||
|
|||
namespace { | |||
|
|||
const bool sDebug = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Likewise, this will need to be set to false before merging.
src/library/searchquery.cpp
Outdated
QString escapedArgument = escaper.escapeString(kSqlLikeMatchAll + m_argument + kSqlLikeMatchAll); | ||
|
||
QSqlQuery query(m_database); | ||
QString queryString = CrateStorage::formatSubselectQueryForCrateTrackIdsByEscapedName(escapedArgument); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function name is really long. Can you think of a shorter one that still explains what the function does?
src/library/searchquery.h
Outdated
void getTrackIds(); | ||
|
||
QSqlDatabase m_database; | ||
QString m_sqlColumn; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unused and can be removed.
@@ -114,10 +117,11 @@ void SearchQueryParser::parseTokens(QStringList tokens, | |||
continue; | |||
} | |||
|
|||
bool negate = token.startsWith(kNegatePrefix); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea to factor this out.
src/library/searchqueryparser.cpp
Outdated
@@ -114,10 +117,11 @@ void SearchQueryParser::parseTokens(QStringList tokens, | |||
continue; | |||
} | |||
|
|||
bool negate = token.startsWith(kNegatePrefix); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are unnecessary space characters on this line. Can you configure your editor to show trailing spaces at the end of a line and/or automatically remove them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, done
src/library/searchqueryparser.cpp
Outdated
if (negate) { | ||
pNode = std::make_unique<NotNode>(std::move(pNode)); | ||
} | ||
pQuery->addNode(std::move(pNode)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This negation logic is repeated for each type of QueryNode. Can it be factored out?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
src/library/searchquery.cpp
Outdated
m_sqlColumn(sqlColumn), | ||
m_argument(argument) { | ||
getTrackIds(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
}
should be the only character on the line.
src/library/searchquery.cpp
Outdated
@@ -52,7 +58,8 @@ QVariant getTrackValueForColumn(const TrackPointer& pTrack, const QString& colum | |||
return static_cast<int>(pTrack->getKey()); | |||
} else if (column == LIBRARYTABLE_BPM_LOCK) { | |||
return pTrack->isBpmLocked(); | |||
} | |||
} // TODO(gramanas) Add getCrates that returns a QStringList with the crates a song is on | |||
// used in CrateFilterNode::match() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this will be necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it was an old plan that I forgot to get rid of
src/library/searchqueryparser.cpp
Outdated
m_allFilters.append(m_numericFilters); | ||
m_allFilters.append(m_specialFilters); | ||
|
||
m_fuzzyMatcher = QRegExp(QString("^~(%1)$").arg(m_allFilters.join("|"))); | ||
m_textFilterMatcher = QRegExp(QString("^-?(%1):(.*)$").arg(m_textFilters.join("|"))); | ||
m_crateFilterMatcher = QRegExp(QString("^-?(crate):(.*)$")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Considering this again, I think it'd be better to reuse m_textFilterMatcher for crates. The way it is currently, both the m_crateFilterMatcher and m_textFilterMatcher regexes are evaluated for each QueryNode that isn't a crate query, which is wasteful. Instead, you could add "crate" to m_textFilters and check if the first capture group in the regex is equal to "crate".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are right, it's more logical to it this way. Done as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking better!
src/library/crate/cratestorage.h
Outdated
@@ -280,8 +280,8 @@ class CrateStorage: public SqlStorage { | |||
CrateId crateId); // no db access | |||
|
|||
// The name must be escaped with FieldEscaper | |||
static QString formatSubselectQueryForCrateTrackIdsByEscapedName( | |||
const QString& crateName); | |||
static QString formatQueryForTrackIdsByEscapedName( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a better name, but it's not clear what name should be passed to the function. How about formatQueryForTrackIdsByCrateName
?
src/library/searchquery.cpp
Outdated
if (it == id) { | ||
return true; | ||
} | ||
if (m_trackIds.contains(id)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still doing string comparison, it's just not explicit anymore. Make m_trackIds
a QList<TrackId>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, you don't need to wrap this in an if
statement. Just return m_trackIds.contains(pTrack->getId())
would work.
I think this is ready for merge. Tests and optimizations can come in the next PR. @uklotzde could you take a look? |
Welcome on board, Anast :) I will be your co-mentor for GSoC 2017. What qualifies me? I recently finished a complete rewrite of the DB code for crates and also did some cleanup in the query and search framework of Mixxx. Great to see that you already started working on the code! This is also what I would propose, try to split your work into manageable sub-tasks that we can review and integrate early. Whenever you notice that you have finished some piece of work that is worth integrating: Create a dedicated branch, cherry-pick or copy those changes from your main development branch, split and tailor these changes into comprehensible commits ("git rebase -i" helps a lot) and open a pull request. I recommend constantly rebasing private development branches instead of merging, to keep the change set narrow and clear. After you published a PR for a branch you should should not (at least not without good reason) rebase it anymore on the current master to keep the comments and diffs in sync. Uwe |
src/library/searchquery.cpp
Outdated
@@ -7,6 +7,12 @@ | |||
#include "library/dao/trackschema.h" | |||
#include "util/db/sqllikewildcards.h" | |||
|
|||
namespace { | |||
|
|||
const bool sDebug = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this kind of hard-coded switches in the code. Unfortunately I noticed that our Logging class does not offer a function to query the current log level! I will shortly publish a small PR that adds the missing functionality.
src/library/searchquery.cpp
Outdated
query.prepare(queryString); | ||
|
||
if (query.exec()) { | ||
if (sDebug) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This conditional would only be necessary if formatting of the debug log message is expensive -> Remove if()
src/library/crate/cratestorage.cpp
Outdated
@@ -410,6 +410,19 @@ QString CrateStorage::formatSubselectQueryForCrateTrackIds( | |||
crateId.toString()); | |||
} | |||
|
|||
//static | |||
QString CrateStorage::formatQueryForTrackIdsByCrateName( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"formatQueryForTrackIdsByCrateNameLike"
src/library/crate/cratestorage.h
Outdated
@@ -279,6 +279,9 @@ class CrateStorage: public SqlStorage { | |||
static QString formatSubselectQueryForCrateTrackIds( | |||
CrateId crateId); // no db access | |||
|
|||
// The name must be escaped with FieldEscaper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Escaping of the crate name argument and surrounding it with LIKE wildcards should be done within this function. The caller should not be aware of this. The CrateStorage class is responsible for shielding the caller from the database-dependent details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this comment after fixing the query
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is not used any more, I think I should delete it as a whole.
src/library/searchquery.cpp
Outdated
|
||
void CrateFilterNode::getTrackIds() { | ||
FieldEscaper escaper(m_database); | ||
QString escapedArgument = escaper.escapeString(kSqlLikeMatchAll + m_argument + kSqlLikeMatchAll); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only escape the argument, not the LIKE wildcards:
kSqlLikeMatchAll + FieldEscaper(m_database).escape(m_argument) + kSqlLikeMatchAll
The result might be equal in this special case, but generally we don't want to escape special SQL characters that are part of the query statement and not part of any parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What you say does not work. I tried it and the query fails execution. Also I copied my code from TextFilterNode who uses the esacaper in the exact same way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ups, sorry, you are right.
src/library/crate/cratestorage.cpp
Outdated
//static | ||
QString CrateStorage::formatQueryForTrackIdsByCrateName( | ||
const QString& crateName) { | ||
return QString("SELECT %1 FROM %2 JOIN %3 ON %4 = %5 WHERE %6 LIKE %7").arg( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You need to use SELECT DISTINCT to avoid duplicate TrackIds if the LIKE statement matches multiple crates.
src/library/searchquery.cpp
Outdated
} | ||
|
||
QString CrateFilterNode::toSql() const { | ||
QStringList trackIds; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This approach is inefficient and doesn't scale well. Please use a subselect similar to formatSubselectQueryForCrateTrackIds():
formatSubselectQueryForCrateTrackIdsByCrateNameLike(const QString& crateNameLike)
src/library/searchquery.cpp
Outdated
const QString& argument) | ||
: m_database(database), | ||
m_argument(argument) { | ||
getTrackIds(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The database query is always executed, even if the result is only needed for match(). See my comments below on how to implement toSql() more efficiently.
src/library/searchquery.cpp
Outdated
getTrackIds(); | ||
} | ||
|
||
void CrateFilterNode::getTrackIds() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A function named "get..." should return a value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can rename it to init() like the NumberFIlterNode
src/library/searchquery.cpp
Outdated
FieldEscaper escaper(m_database); | ||
QString escapedArgument = escaper.escapeString(kSqlLikeMatchAll + m_argument + kSqlLikeMatchAll); | ||
|
||
QSqlQuery query(m_database); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be accomplished much more efficiently:
- New member function: CrateTrackSelectResult CrateStorage::selectCrateTracksSortedByCrateNameLike(const QString& crateNameLike) const
-- Very similar to CrateStorage selectCrateTracksSorted()
-- Instead of providing a single crateId provide a crateNameLike argument and do a sub-select (that we also need for CrateFilterNode::toSql()) - CrateFilterNode: Initialization
-- Perform DB query
-- Ignore crateId()s
-- Collect all distinct trackIds()s in a std::vector
-- Skip duplicate trackId()s resulting from multiple crates matching the LIKE query - CrateFilterNode::match()
-- Use std::binary_search() on the sorted vector for fast O(log(n)) lookup
Please refer to CrateFeature for a similar use case.
Optimization: Use lazy evaluation, i.e. the first call to match() should trigger the database query and populate the result set once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The initialization already has all distinct trackIds in a vector. Why whould match trigger a db query again?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignore my comment about the possible optimization for now. But get to know what "lazy initialization/evaluation" means ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CrateTrackSelectResult CrateStorage::selectCrateTracksSortedByCrateNameLike(const QString& crateNameLike) const
Ah, I see. By returning a CrateTrackSelectResult, CrateStorage can take care of everything touching the DB so the CrateFilterNode only needs the const reference to CrateStorage from TrackCollection::crates() instead of the reference to the QSqlDatabase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optimization: Use lazy evaluation, i.e. the first call to match() should trigger the database query and populate the result set once.
But toSql() needs the results of the DB query before match() is called. Also both of those methods are const so they can't be used to store the result set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, toSql() and match() serve a different purpose. toSql() is used to query the database while match() is only applied for post-filtering all tracks that are already cached in memory, e.g. all dirty tracks that have been modified recently. Without any dirty tracks match() will not be invoked!
You can circumvent the const-correctness by using mutable members behind the scenes. But we can add this optimization later and avoid those dirty tricks ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CrateStorage should encapsulate access to crate tables as much as possible. It's an anti-corruption layer between the database and clients, basic OO ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it'd be helpful to add to the comment in src/util/db/fwdsqlqueryselectresult.h that DAO classes should implement methods returning FwdSqlQuerySelectResult objects when a caller wants info from the DB so callers don't need access to the DB directly?
src/library/searchquery.cpp
Outdated
} | ||
|
||
bool CrateFilterNode::match(const TrackPointer& pTrack) const { | ||
return m_trackIds.contains(pTrack->getId()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lookup in a QList doesn't scale well: O(n)
But this becomes obsolete anyway with my proposal for an optimized DB query.
@@ -5,8 +5,9 @@ | |||
const char* kNegatePrefix = "-"; | |||
const char* kFuzzyPrefix = "~"; | |||
|
|||
SearchQueryParser::SearchQueryParser(QSqlDatabase& database) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test for the SearchQueryParser uses this constructor
and I couldn't figure out a way to get rid of it.
If you see the code it connects it to a new instance of the DB.
In the test there is no mention of a TrackCollection so SearchQueryParser
has two constructors now. Thankfully the constructor is only
called once in the whole codebase (at TrackCollection's constructor)
so it's not a huge problem. But I don't like it. From what I understand
it's normal to have functions that are used only for testing
so my concern might be a bit off.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking better. Still some room for improvement.
src/library/crate/cratestorage.cpp
Outdated
@@ -453,6 +453,23 @@ CrateTrackSelectResult CrateStorage::selectTrackCratesSorted(TrackId trackId) co | |||
} | |||
} | |||
|
|||
CrateTrackSelectResult CrateStorage::selectTracksSortedByCrateNameLike(const QString& crateNameLike) const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function should be responsible for the escaping. The caller shouldn't need to worry about that detail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I got confused cause the other function was static and had no access to the m_database
of CrateStorage. Should be ok now.
src/library/searchqueryparser.cpp
Outdated
@@ -128,7 +129,7 @@ void SearchQueryParser::parseTokens(QStringList tokens, | |||
if (!argument.isEmpty()) { | |||
if (field == "crate") { | |||
pNode = std::make_unique<CrateFilterNode>( | |||
m_database, argument); | |||
argument, m_pTrackCollection); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CrateFilterNode shouldn't need the TrackCollection pointer, just the CrateStorage reference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I though I could make it more general, but I guess there is no need.
src/library/crate/cratestorage.cpp
Outdated
@@ -440,6 +453,23 @@ CrateTrackSelectResult CrateStorage::selectTrackCratesSorted(TrackId trackId) co | |||
} | |||
} | |||
|
|||
CrateTrackSelectResult CrateStorage::selectTracksSortedByCrateNameLike(const QString& crateNameLike) const { | |||
FwdSqlQuery query(m_database, QString( | |||
"SELECT DISTINCT %1,%2 FROM %3 JOIN %4 ON %5 = %6 WHERE %7 LIKE %8 ORDER BY %1").arg( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DISTINCT is redundant here and should be omitted
@@ -287,6 +290,8 @@ class CrateStorage: public SqlStorage { | |||
CrateId crateId) const; | |||
CrateTrackSelectResult selectTrackCratesSorted( | |||
TrackId trackId) const; | |||
CrateTrackSelectResult selectTracksSortedByCrateNameLike( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"selectCrateTracks..." to follow the naming style of its siblings. The result contains (crateId, trackId) tuples and not only trackIds.
Now that we added a new function we could think about how to improve the naming of all related functions. But finally it it should be consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the interest of minimizing potential merge conflicts with #1117, I think we should hold off on renaming things for now.
src/library/crate/cratestorage.cpp
Outdated
CRATETABLE_ID, | ||
CRATETRACKSTABLE_CRATEID, | ||
CRATETABLE_NAME, | ||
crateNameLike)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please bind crateNameLike as a parameter by-value to the query instead of formatting it into the string. Then you don't need to escape it manually
"...%7 LIKE :crateNameLike..."
query.bindValue(":crateNameLike", crateNameLike);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This does not work as you say. The query fails if I don't use escaper
src/library/searchquery.cpp
Outdated
|
||
void CrateFilterNode::init() { | ||
FieldEscaper escaper(m_pTrackCollection->database()); | ||
QString escapedArgument = escaper.escapeString(kSqlLikeMatchAll + m_argument + kSqlLikeMatchAll); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't escape. When binding parameters to queries you almost never need to escape manually. Everything in queryutil.h is legacy code that shouldn't be used any more. I didn't remember that I eliminated it from the refactored CrateStorage ;)
src/library/searchquery.h
Outdated
|
||
bool match(const TrackPointer& pTrack) const override; | ||
QString toSql() const override; | ||
|
||
private: | ||
void init(); | ||
|
||
QSqlDatabase m_database; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unused now.
src/library/searchquery.cpp
Outdated
return std::binary_search(m_trackIds.begin(), m_trackIds.end(), pTrack->getId()); | ||
} | ||
|
||
QString CrateFilterNode::toSql() const { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code simply doesn't scale. For example I have crates with thousands of tracks that I use for storing pre-computed searches.
Subselect: "id IN (" + CrateStorage::formatSubselectQueryForCrateTrackIdsWhereCrateNameLike(crateNameLike) + ")"
Please note, that you need to escape crateNameLike manually when formatting the subselect string! We don't have access to the SQL query of the caller and can't bind the parameter value by name here.
I also came to the conclusion that we might use "Where" instead of "By" for the filtering condition in those function names. Terms like "...SortedBy..." are simply misleading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I think I got it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this run the subselect query twice, once as a subquery in the main search filter query, and again to get the list of TrackIds for match()? That would be faster than building a QStringList from the vector of TrackIds?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed, right now I run a query in CrateFilterNode::Init() function to fill the vector, and then I return the same SQL to be run by the query in BaseTrackCache::filterAndSort(). Maybe by using lazy evaluation this would work, but right now I run the query 2 times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If that's the case you could move the query from the init method to the match method. If you don't need to store any information for later use, you can use a local variable for the TrackId vector. That way the second query will only be run if it is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A local variable does not work here and a static local variable would be wrong. For lazy initialization you need to populate a (mutable) vector m_matchingTrackIds upon the first invocation of match() and use another (mutable) member variable m_matchingTrackIdsInitialized for tracking this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, so I remove the query execution from init(), I lazy initialize it in match and I leave toSql as it is, to return a SQL query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought match() was only called once in the life of the QueryNode? If so, why do the TrackIds need to be stored?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, nevermind. match() is called for each dirty TrackId.
src/library/crate/cratestorage.cpp
Outdated
@@ -441,6 +457,26 @@ CrateTrackSelectResult CrateStorage::selectTrackCratesSorted(TrackId trackId) co | |||
} | |||
|
|||
|
|||
CrateTrackSelectResult CrateStorage::selectTracksSortedByCrateNameLike(const QString& crateName) const { | |||
FieldEscaper escaper(m_database); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As already mentioned, please bind crateName as a named parameter to the query using bindValue(). FieldEscaper then becomes obsolete here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried it the last time you mentioned it, and it didn't work. The name needs to be wrapped in kSqlLikeMatchAll (%). Also it needs to be inside quotes. bindValue() does not seem to do that. I can use it like this:
bindValue(kSqlLikeMatchAll + crateNameLike + kSqlLikeMatchAll)
src/library/searchquery.cpp
Outdated
} | ||
|
||
QString CrateFilterNode::toSql() const { | ||
return QString("id IN (%1)").arg(CrateStorage::formatQueryForTrackIdsByCrateNameLike( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you make formatQueryForTrackIdsByCrateNameLike() a non-static member function of CrateStorage you can get rid of the awkward database parameter and also the m_database member of CrateFilterNode. You are storing a pointer to CrateStorage anyway which already carries a database connection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got rid of field escaper instead witch used the database connection.
src/library/searchquery.h
Outdated
@@ -14,6 +14,8 @@ | |||
#include "proto/keys.pb.h" | |||
#include "util/assert.h" | |||
#include "util/memory.h" | |||
#include "library/trackcollection.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This #include doesn't seem to be needed.
src/library/searchquery.h
Outdated
class CrateFilterNode : public QueryNode { | ||
public: | ||
CrateFilterNode(const QSqlDatabase& database, | ||
const QString& argument, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Give argument a meaning by using a more expressive name: crateNameLike
src/library/searchquery.h
Outdated
|
||
private: | ||
QSqlDatabase m_database; | ||
QString m_argument; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...also here: m_crateNameLike
src/library/searchquery.h
Outdated
QString toSql() const override; | ||
|
||
private: | ||
QSqlDatabase m_database; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This member should vanish according to my comment regarding the database connection
src/library/searchqueryparser.h
Outdated
@@ -11,14 +11,19 @@ | |||
|
|||
class SearchQueryParser { | |||
public: | |||
SearchQueryParser(QSqlDatabase& database); | |||
SearchQueryParser(TrackCollection* pTrackCollection); | |||
SearchQueryParser(QSqlDatabase& database) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This constructor shouldn't be available anymore! Otherwise m_pTrackCollection is uninitialized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check my comment on:
#1263 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the test for searchqueryparser should take a TrackCollection instead of just a database connection.
src/library/searchqueryparser.h
Outdated
@@ -27,6 +32,8 @@ class SearchQueryParser { | |||
QString getTextArgument(QString argument, | |||
QStringList* tokens) const; | |||
|
|||
TrackCollection* m_pTrackCollection; | |||
|
|||
QSqlDatabase m_database; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
m_database is redundant and should instead be obtained from m_pTrackCollection if needed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also related to the previous comment. If I get rid of this, then the test class can't work
because it uses the constructor that attaches a connection to m_database
src/library/searchqueryparser.h
Outdated
@@ -11,14 +11,19 @@ | |||
|
|||
class SearchQueryParser { | |||
public: | |||
SearchQueryParser(QSqlDatabase& database); | |||
SearchQueryParser(TrackCollection* pTrackCollection); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Single argument constructors should be declared 'explicit by default! Otherwise the compiler will apply an implicit conversion from TrackCollection* to SearchQuery whenever it needs to without any warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you could explain that a little more! I understand what explicit and implicit conversion is, but I don't really get your point here. Also by your logic the
SearchQueryParser(QSqlDatabase& database);
constructor that was there was also wrong and should be explicit right?
src/library/searchqueryparser.h
Outdated
@@ -36,6 +43,7 @@ class SearchQueryParser { | |||
|
|||
QRegExp m_fuzzyMatcher; | |||
QRegExp m_textFilterMatcher; | |||
QRegExp m_crateFilterMatcher; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You forgot to remove this now that m_textFilterMatcher is used for the crate filter.
I did a rebase to clean the commit history from all these minor fixes etc. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a minor change to make, otherwise looks good. What do you think @uklotzde?
src/library/searchqueryparser.cpp
Outdated
pNode = std::make_unique<NotNode>(std::move(pNode)); | ||
if (field == "crate") { | ||
pNode = std::make_unique<CrateFilterNode>( | ||
argument, &m_pTrackCollection->crates()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
argument
is the last... argument... for the constructors of the other QueryNodes. I think the CrateFilterNode should be consistent with that pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also noticed this, but didn't want to confuse you with minor issues ;) Method parameters should be ordered starting from broad, context-related arguments to more specialized arguments. Ok, since we both agree let's fix it now.
src/test/searchqueryparsertest.cpp
Outdated
protected: | ||
SearchQueryParserTest() | ||
: m_database(QSqlDatabase::addDatabase("QSQLITE")), | ||
m_parser(m_database) { | ||
m_parser(collection()) { | ||
QTemporaryFile databaseFile("mixxxdb.sqlite"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the manually created connection to a temporary database. It should be obsolete now since LibraryTest provides a TrackCollection with a database.
Just a recommendation for a readable Git commit history: "Small fixes" is just random noise. Please use more meaningful phrases like "Reorder constructor arguments" and "Remove unused database connection from test" instead. You may also notice that I prefer imperative verbs instead of past tense. That's a common recommendation, more readable and uses fewer characters ;) |
I will do a final test. |
LGTM |
Does this mean, I can merge this to master? Or do we now have valid reason the cut out 2.1? |
@gramanas: Sorry, I was not able to follow all the changes in this PR. Could you update the initial commit description, including short hint what are the noteable/testable changes? |
Add CrateFilterNode in library/searchquery.h witch is responsible for handling the crate filtering. Modify searchqueryParser.cpp to handle the crate keyword and create a CrateFIlterNode. Finally add a few functions in cratestorage.cpp to handle the SQL queries needed for the filter to work. Also change searchqueryparsertest.cpp due to the new SearchQueryParser constructor.
The changes are very limited and the final code quality is really good. I don't see any reason why we should hold this back. |
@daschuer Ok, I added a bit more in the commit message. |
Thank you all, working on this! |
Yes, thank you @gramanas and reviewers! This is something that will be super useful immediately. I'm keen to try it out :) |
Hello, my first PR for GSoC.
Also my first PR ever!
I implemented a new
CrateFilterNode
witch is responsible for the crate: filter at search.It queries the database to get a list of
track_Ids
witch is then added to the final querythat gets executed at
BaseTrackCache::filterAndSort()
.TODO's
Please have a look at the code and leave your comments.