diff options
author | Minijackson <minijackson@riseup.net> | 2018-05-25 11:28:22 +0200 |
---|---|---|
committer | Minijackson <minijackson@riseup.net> | 2018-05-25 11:29:30 +0200 |
commit | 00717f6c8b8a9c6dbd56a80d685c5082fc03f6a5 (patch) | |
tree | eb0871b7518234c3db3e2d647b0b7c020253accb /common/typeindex.cpp | |
parent | c095e82143fd16c84263d990b96590b3b0d12a78 (diff) | |
download | sink-00717f6c8b8a9c6dbd56a80d685c5082fc03f6a5.tar.gz sink-00717f6c8b8a9c6dbd56a80d685c5082fc03f6a5.zip |
Implement range queries
Diffstat (limited to 'common/typeindex.cpp')
-rw-r--r-- | common/typeindex.cpp | 122 |
1 files changed, 110 insertions, 12 deletions
diff --git a/common/typeindex.cpp b/common/typeindex.cpp index a897ad0..5564144 100644 --- a/common/typeindex.cpp +++ b/common/typeindex.cpp | |||
@@ -21,9 +21,12 @@ | |||
21 | #include "log.h" | 21 | #include "log.h" |
22 | #include "index.h" | 22 | #include "index.h" |
23 | #include "fulltextindex.h" | 23 | #include "fulltextindex.h" |
24 | |||
24 | #include <QDateTime> | 25 | #include <QDateTime> |
25 | #include <QDataStream> | 26 | #include <QDataStream> |
26 | 27 | ||
28 | #include <cmath> | ||
29 | |||
27 | using namespace Sink; | 30 | using namespace Sink; |
28 | 31 | ||
29 | static QByteArray getByteArray(const QVariant &value) | 32 | static QByteArray getByteArray(const QVariant &value) |
@@ -50,15 +53,34 @@ static QByteArray getByteArray(const QVariant &value) | |||
50 | return "toplevel"; | 53 | return "toplevel"; |
51 | } | 54 | } |
52 | 55 | ||
53 | static QByteArray toSortableByteArray(const QDateTime &date) | 56 | |
57 | static QByteArray toSortableByteArrayImpl(const QDateTime &date) | ||
54 | { | 58 | { |
55 | // Sort invalid last | 59 | // Sort invalid last |
56 | if (!date.isValid()) { | 60 | if (!date.isValid()) { |
57 | return QByteArray::number(std::numeric_limits<unsigned int>::max()); | 61 | return QByteArray::number(std::numeric_limits<unsigned int>::max()); |
58 | } | 62 | } |
59 | return QByteArray::number(std::numeric_limits<unsigned int>::max() - date.toTime_t()); | 63 | static unsigned int uint_num_digits = std::log10(std::numeric_limits<unsigned int>::max()) + 1; |
64 | return QByteArray::number(std::numeric_limits<unsigned int>::max() - date.toTime_t()).rightJustified(uint_num_digits, '0'); | ||
60 | } | 65 | } |
61 | 66 | ||
67 | static QByteArray toSortableByteArray(const QVariant &value) | ||
68 | { | ||
69 | if (!value.isValid()) { | ||
70 | // FIXME: we don't know the type, so we don't know what to return | ||
71 | // This mean we're fixing every sorted index keys to use unsigned int | ||
72 | return QByteArray::number(std::numeric_limits<unsigned int>::max()); | ||
73 | } | ||
74 | |||
75 | switch (value.type()) { | ||
76 | case QMetaType::QDateTime: | ||
77 | return toSortableByteArrayImpl(value.toDateTime()); | ||
78 | default: | ||
79 | SinkWarning() << "Not knowing how to convert a" << value.typeName() | ||
80 | << "to a sortable key, falling back to default conversion"; | ||
81 | return getByteArray(value); | ||
82 | } | ||
83 | } | ||
62 | 84 | ||
63 | TypeIndex::TypeIndex(const QByteArray &type, const Sink::Log::Context &ctx) : mLogCtx(ctx), mType(type) | 85 | TypeIndex::TypeIndex(const QByteArray &type, const Sink::Log::Context &ctx) : mLogCtx(ctx), mType(type) |
64 | { | 86 | { |
@@ -72,6 +94,11 @@ QByteArray TypeIndex::indexName(const QByteArray &property, const QByteArray &so | |||
72 | return mType + ".index." + property + ".sort." + sortProperty; | 94 | return mType + ".index." + property + ".sort." + sortProperty; |
73 | } | 95 | } |
74 | 96 | ||
97 | QByteArray TypeIndex::sortedIndexName(const QByteArray &property) const | ||
98 | { | ||
99 | return mType + ".index." + property + ".sorted"; | ||
100 | } | ||
101 | |||
75 | template <> | 102 | template <> |
76 | void TypeIndex::addProperty<QByteArray>(const QByteArray &property) | 103 | void TypeIndex::addProperty<QByteArray>(const QByteArray &property) |
77 | { | 104 | { |
@@ -138,6 +165,22 @@ void TypeIndex::addProperty<ApplicationDomain::Reference>(const QByteArray &prop | |||
138 | } | 165 | } |
139 | 166 | ||
140 | template <> | 167 | template <> |
168 | void TypeIndex::addSortedProperty<QDateTime>(const QByteArray &property) | ||
169 | { | ||
170 | auto indexer = [this, property](bool add, const QByteArray &identifier, const QVariant &value, | ||
171 | Sink::Storage::DataStore::Transaction &transaction) { | ||
172 | const auto sortableDate = toSortableByteArray(value); | ||
173 | if (add) { | ||
174 | Index(sortedIndexName(property), transaction).add(sortableDate, identifier); | ||
175 | } else { | ||
176 | Index(sortedIndexName(property), transaction).remove(sortableDate, identifier); | ||
177 | } | ||
178 | }; | ||
179 | mSortIndexer.insert(property, indexer); | ||
180 | mSortedProperties << property; | ||
181 | } | ||
182 | |||
183 | template <> | ||
141 | void TypeIndex::addPropertyWithSorting<QByteArray, QDateTime>(const QByteArray &property, const QByteArray &sortProperty) | 184 | void TypeIndex::addPropertyWithSorting<QByteArray, QDateTime>(const QByteArray &property, const QByteArray &sortProperty) |
142 | { | 185 | { |
143 | auto indexer = [=](bool add, const QByteArray &identifier, const QVariant &value, const QVariant &sortValue, Sink::Storage::DataStore::Transaction &transaction) { | 186 | auto indexer = [=](bool add, const QByteArray &identifier, const QVariant &value, const QVariant &sortValue, Sink::Storage::DataStore::Transaction &transaction) { |
@@ -149,8 +192,8 @@ void TypeIndex::addPropertyWithSorting<QByteArray, QDateTime>(const QByteArray & | |||
149 | Index(indexName(property, sortProperty), transaction).remove(propertyValue + toSortableByteArray(date), identifier); | 192 | Index(indexName(property, sortProperty), transaction).remove(propertyValue + toSortableByteArray(date), identifier); |
150 | } | 193 | } |
151 | }; | 194 | }; |
152 | mSortIndexer.insert(property + sortProperty, indexer); | 195 | mGroupedSortIndexer.insert(property + sortProperty, indexer); |
153 | mSortedProperties.insert(property, sortProperty); | 196 | mGroupedSortedProperties.insert(property, sortProperty); |
154 | } | 197 | } |
155 | 198 | ||
156 | template <> | 199 | template <> |
@@ -166,10 +209,15 @@ void TypeIndex::updateIndex(bool add, const QByteArray &identifier, const Sink:: | |||
166 | auto indexer = mIndexer.value(property); | 209 | auto indexer = mIndexer.value(property); |
167 | indexer(add, identifier, value, transaction); | 210 | indexer(add, identifier, value, transaction); |
168 | } | 211 | } |
169 | for (auto it = mSortedProperties.constBegin(); it != mSortedProperties.constEnd(); it++) { | 212 | for (const auto &property : mSortedProperties) { |
213 | const auto value = entity.getProperty(property); | ||
214 | auto indexer = mSortIndexer.value(property); | ||
215 | indexer(add, identifier, value, transaction); | ||
216 | } | ||
217 | for (auto it = mGroupedSortedProperties.constBegin(); it != mGroupedSortedProperties.constEnd(); it++) { | ||
170 | const auto value = entity.getProperty(it.key()); | 218 | const auto value = entity.getProperty(it.key()); |
171 | const auto sortValue = entity.getProperty(it.value()); | 219 | const auto sortValue = entity.getProperty(it.value()); |
172 | auto indexer = mSortIndexer.value(it.key() + it.value()); | 220 | auto indexer = mGroupedSortIndexer.value(it.key() + it.value()); |
173 | indexer(add, identifier, value, sortValue, transaction); | 221 | indexer(add, identifier, value, sortValue, transaction); |
174 | } | 222 | } |
175 | for (const auto &indexer : mCustomIndexer) { | 223 | for (const auto &indexer : mCustomIndexer) { |
@@ -207,22 +255,61 @@ void TypeIndex::remove(const QByteArray &identifier, const Sink::ApplicationDoma | |||
207 | updateIndex(false, identifier, entity, transaction, resourceInstanceId); | 255 | updateIndex(false, identifier, entity, transaction, resourceInstanceId); |
208 | } | 256 | } |
209 | 257 | ||
210 | static QVector<QByteArray> indexLookup(Index &index, QueryBase::Comparator filter) | 258 | static QVector<QByteArray> indexLookup(Index &index, QueryBase::Comparator filter, |
259 | std::function<QByteArray(const QVariant &)> valueToKey = getByteArray) | ||
211 | { | 260 | { |
212 | QVector<QByteArray> keys; | 261 | QVector<QByteArray> keys; |
213 | QByteArrayList lookupKeys; | 262 | QByteArrayList lookupKeys; |
214 | if (filter.comparator == Query::Comparator::Equals) { | 263 | if (filter.comparator == Query::Comparator::Equals) { |
215 | lookupKeys << getByteArray(filter.value); | 264 | lookupKeys << valueToKey(filter.value); |
216 | } else if (filter.comparator == Query::Comparator::In) { | 265 | } else if (filter.comparator == Query::Comparator::In) { |
217 | lookupKeys = filter.value.value<QByteArrayList>(); | 266 | for(const QVariant &value : filter.value.value<QVariantList>()) { |
267 | lookupKeys << valueToKey(value); | ||
268 | } | ||
269 | //lookupKeys = filter.value.value<QByteArrayList>(); | ||
218 | } else { | 270 | } else { |
219 | Q_ASSERT(false); | 271 | Q_ASSERT(false); |
220 | } | 272 | } |
221 | 273 | ||
222 | for (const auto &lookupKey : lookupKeys) { | 274 | for (const auto &lookupKey : lookupKeys) { |
223 | index.lookup(lookupKey, [&](const QByteArray &value) { keys << value; }, | 275 | index.lookup(lookupKey, [&](const QByteArray &value) { keys << value; }, |
224 | [lookupKey](const Index::Error &error) { SinkWarning() << "Lookup error in index: " << error.message << lookupKey; }, true); | 276 | [lookupKey](const Index::Error &error) { |
277 | SinkWarning() << "Lookup error in index: " << error.message << lookupKey; | ||
278 | }, | ||
279 | true); | ||
280 | } | ||
281 | return keys; | ||
282 | } | ||
283 | |||
284 | static QVector<QByteArray> sortedIndexLookup(Index &index, QueryBase::Comparator filter) | ||
285 | { | ||
286 | if (filter.comparator == Query::Comparator::In || filter.comparator == Query::Comparator::Contains) { | ||
287 | SinkWarning() << "In and Contains comparison not supported on sorted indexes"; | ||
288 | } | ||
289 | |||
290 | if (filter.comparator != Query::Comparator::Within) { | ||
291 | return indexLookup(index, filter, toSortableByteArray); | ||
292 | } | ||
293 | |||
294 | QVector<QByteArray> keys; | ||
295 | |||
296 | QByteArray lowerBound, upperBound; | ||
297 | auto bounds = filter.value.value<QVariantList>(); | ||
298 | if (bounds[0].canConvert<QDateTime>()) { | ||
299 | // Inverse the bounds because dates are stored newest first | ||
300 | upperBound = toSortableByteArray(bounds[0].toDateTime()); | ||
301 | lowerBound = toSortableByteArray(bounds[1].toDateTime()); | ||
302 | } else { | ||
303 | lowerBound = bounds[0].toByteArray(); | ||
304 | upperBound = bounds[1].toByteArray(); | ||
225 | } | 305 | } |
306 | |||
307 | index.rangeLookup(lowerBound, upperBound, [&](const QByteArray &value) { keys << value; }, | ||
308 | [bounds](const Index::Error &error) { | ||
309 | SinkWarning() << "Lookup error in index:" << error.message | ||
310 | << "with bounds:" << bounds[0] << bounds[1]; | ||
311 | }); | ||
312 | |||
226 | return keys; | 313 | return keys; |
227 | } | 314 | } |
228 | 315 | ||
@@ -239,16 +326,27 @@ QVector<QByteArray> TypeIndex::query(const Sink::QueryBase &query, QSet<QByteArr | |||
239 | } | 326 | } |
240 | } | 327 | } |
241 | 328 | ||
242 | for (auto it = mSortedProperties.constBegin(); it != mSortedProperties.constEnd(); it++) { | 329 | for (auto it = mGroupedSortedProperties.constBegin(); it != mGroupedSortedProperties.constEnd(); it++) { |
243 | if (query.hasFilter(it.key()) && query.sortProperty() == it.value()) { | 330 | if (query.hasFilter(it.key()) && query.sortProperty() == it.value()) { |
244 | Index index(indexName(it.key(), it.value()), transaction); | 331 | Index index(indexName(it.key(), it.value()), transaction); |
245 | const auto keys = indexLookup(index, query.getFilter(it.key())); | 332 | const auto keys = indexLookup(index, query.getFilter(it.key())); |
246 | appliedFilters << it.key(); | 333 | appliedFilters << it.key(); |
247 | appliedSorting = it.value(); | 334 | appliedSorting = it.value(); |
248 | SinkTraceCtx(mLogCtx) << "Sorted index lookup on " << it.key() << it.value() << " found " << keys.size() << " keys."; | 335 | SinkTraceCtx(mLogCtx) << "Grouped sorted index lookup on " << it.key() << it.value() << " found " << keys.size() << " keys."; |
249 | return keys; | 336 | return keys; |
250 | } | 337 | } |
251 | } | 338 | } |
339 | |||
340 | for (const auto &property : mSortedProperties) { | ||
341 | if (query.hasFilter(property)) { | ||
342 | Index index(sortedIndexName(property), transaction); | ||
343 | const auto keys = sortedIndexLookup(index, query.getFilter(property)); | ||
344 | appliedFilters << property; | ||
345 | SinkTraceCtx(mLogCtx) << "Sorted index lookup on " << property << " found " << keys.size() << " keys."; | ||
346 | return keys; | ||
347 | } | ||
348 | } | ||
349 | |||
252 | for (const auto &property : mProperties) { | 350 | for (const auto &property : mProperties) { |
253 | if (query.hasFilter(property)) { | 351 | if (query.hasFilter(property)) { |
254 | Index index(indexName(property), transaction); | 352 | Index index(indexName(property), transaction); |