From 2e709350000205115297167ca5d32d1cbb4168bc Mon Sep 17 00:00:00 2001 From: James Stone Date: Tue, 13 Aug 2019 18:05:51 -0700 Subject: [PATCH] Add GreaterEqual and LessEqual specializations We take the position that you cannot query a timestamp column using <, >, <= and >= against a null value. --- CHANGELOG.md | 1 + src/realm/query_engine.cpp | 76 +++++++++++++++++++++++++ src/realm/query_engine.hpp | 11 ++++ test/benchmark-common-tasks/main.cpp | 64 ++++++++++----------- test/test_query.cpp | 22 ++++++++ test/test_shared.cpp | 84 ++++++---------------------- 6 files changed, 160 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b0564b2468..7aed0b4111f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Internals * The release binaries for Apple platforms are now built with Xcode 9.4 (up from 9.2). +* Performance of queries on Timestamp is improved ---------------------------------------------- diff --git a/src/realm/query_engine.cpp b/src/realm/query_engine.cpp index 967b5ddacd8..0ab90fcc110 100644 --- a/src/realm/query_engine.cpp +++ b/src/realm/query_engine.cpp @@ -719,6 +719,7 @@ size_t TimestampNode::find_first_local(size_t start, size_t end) return not_found; } + template <> size_t TimestampNode::find_first_local(size_t start, size_t end) { @@ -752,4 +753,79 @@ size_t TimestampNode::find_first_local(size_t start, size_t end) return not_found; } + +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end) +{ + REALM_ASSERT(this->m_table); + + if (this->m_value.is_null()) { + return not_found; + } + while (start < end) { + size_t ret = this->find_first_local_seconds(start, end); + + if (ret == not_found) + return not_found; + + util::Optional seconds = get_seconds_and_cache(ret); + if (!seconds) { // null equality + start = ret + 1; + continue; + } + if (*seconds > m_value.get_seconds()) { + return ret; + } + // We now know that neither m_value nor current value is null and that seconds part equals + // We are just missing to compare nanoseconds part + int32_t nanos = this->get_nanoseconds_and_cache(ret); + if (nanos >= m_value.get_nanoseconds()) { + return ret; + } + start = ret + 1; + } + + return not_found; +} + +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end) +{ + REALM_ASSERT(this->m_table); + + if (this->m_value.is_null()) { + return not_found; + } + while (start < end) { + size_t ret = this->find_first_local_seconds(start, end); + + if (ret == not_found) + return not_found; + + util::Optional seconds = get_seconds_and_cache(ret); + if (!seconds) { // null equality + start = ret + 1; + continue; + } + if (*seconds < m_value.get_seconds()) { + return ret; + } + // We now know that neither m_value nor current value is null and that seconds part equals + // We are just missing to compare nanoseconds part + int32_t nanos = this->get_nanoseconds_and_cache(ret); + if (nanos <= m_value.get_nanoseconds()) { + return ret; + } + start = ret + 1; + } + + return not_found; +} +#ifdef _WIN32 +// Explicit instantiation required on some windows builds +template size_t TimestampNode::find_first_local(size_t start, size_t end); +template size_t TimestampNode::find_first_local(size_t start, size_t end); +template size_t TimestampNode::find_first_local(size_t start, size_t end); +template size_t TimestampNode::find_first_local(size_t start, size_t end); +#endif } // namespace realm diff --git a/src/realm/query_engine.hpp b/src/realm/query_engine.hpp index e78be60b849..7f87be1bf3c 100644 --- a/src/realm/query_engine.hpp +++ b/src/realm/query_engine.hpp @@ -1452,6 +1452,17 @@ class TimestampNode : public TimestampNodeBase { } }; +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end); +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end); +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end); +template <> +size_t TimestampNode::find_first_local(size_t start, size_t end); + + + class StringNodeBase : public ParentNode { public: using TConditionValue = StringData; diff --git a/test/benchmark-common-tasks/main.cpp b/test/benchmark-common-tasks/main.cpp index f406a83e25d..601dbed1bf0 100644 --- a/test/benchmark-common-tasks/main.cpp +++ b/test/benchmark-common-tasks/main.cpp @@ -1233,38 +1233,38 @@ int benchmark_common_tasks_main() #define BENCH(B) run_benchmark(results) -// BENCH(BenchmarkUnorderedTableViewClear); -// BENCH(BenchmarkEmptyCommit); -// BENCH(AddTable); -// BENCH(BenchmarkQuery); -// BENCH(BenchmarkQueryNot); -// BENCH(BenchmarkSize); -// BENCH(BenchmarkSort); -// BENCH(BenchmarkSortInt); -// BENCH(BenchmarkDistinctIntFewDupes); -// BENCH(BenchmarkDistinctIntManyDupes); -// BENCH(BenchmarkDistinctStringFewDupes); -// BENCH(BenchmarkDistinctStringManyDupes); -// BENCH(BenchmarkFindAllStringFewDupes); -// BENCH(BenchmarkFindAllStringManyDupes); -// BENCH(BenchmarkFindFirstStringFewDupes); -// BENCH(BenchmarkFindFirstStringManyDupes); -// BENCH(BenchmarkInsert); -// BENCH(BenchmarkGetString); -// BENCH(BenchmarkSetString); -// BENCH(BenchmarkCreateIndex); -// BENCH(BenchmarkGetLongString); -// BENCH(BenchmarkQueryLongString); -// BENCH(BenchmarkSetLongString); -// BENCH(BenchmarkGetLinkList); -// BENCH(BenchmarkQueryInsensitiveString); -// BENCH(BenchmarkQueryInsensitiveStringIndexed); -// BENCH(BenchmarkNonInitatorOpen); -// BENCH(BenchmarkQueryChainedOrStrings); -// BENCH(BenchmarkQueryChainedOrInts); -// BENCH(BenchmarkQueryChainedOrIntsIndexed); -// BENCH(BenchmarkQueryIntEquality); -// BENCH(BenchmarkQueryIntEqualityIndexed); + BENCH(BenchmarkUnorderedTableViewClear); + BENCH(BenchmarkEmptyCommit); + BENCH(AddTable); + BENCH(BenchmarkQuery); + BENCH(BenchmarkQueryNot); + BENCH(BenchmarkSize); + BENCH(BenchmarkSort); + BENCH(BenchmarkSortInt); + BENCH(BenchmarkDistinctIntFewDupes); + BENCH(BenchmarkDistinctIntManyDupes); + BENCH(BenchmarkDistinctStringFewDupes); + BENCH(BenchmarkDistinctStringManyDupes); + BENCH(BenchmarkFindAllStringFewDupes); + BENCH(BenchmarkFindAllStringManyDupes); + BENCH(BenchmarkFindFirstStringFewDupes); + BENCH(BenchmarkFindFirstStringManyDupes); + BENCH(BenchmarkInsert); + BENCH(BenchmarkGetString); + BENCH(BenchmarkSetString); + BENCH(BenchmarkCreateIndex); + BENCH(BenchmarkGetLongString); + BENCH(BenchmarkQueryLongString); + BENCH(BenchmarkSetLongString); + BENCH(BenchmarkGetLinkList); + BENCH(BenchmarkQueryInsensitiveString); + BENCH(BenchmarkQueryInsensitiveStringIndexed); + BENCH(BenchmarkNonInitatorOpen); + BENCH(BenchmarkQueryChainedOrStrings); + BENCH(BenchmarkQueryChainedOrInts); + BENCH(BenchmarkQueryChainedOrIntsIndexed); + BENCH(BenchmarkQueryIntEquality); + BENCH(BenchmarkQueryIntEqualityIndexed); BENCH(BenchmarkQueryTimestampGreater); BENCH(BenchmarkQueryTimestampGreaterEqual); diff --git a/test/test_query.cpp b/test/test_query.cpp index 7df0d5b4169..ca005f2c28f 100644 --- a/test/test_query.cpp +++ b/test/test_query.cpp @@ -10685,6 +10685,28 @@ TEST(Query_Timestamp) CHECK_EQUAL(match, npos); // Note that (null < null) == false } +TEST(Query_TimestampCount) +{ + Table table; + auto col_date = table.add_column(type_Timestamp, "date", true); + for (int i = 0; i < 10; i++) { + auto ndx = table.add_empty_row(); + table.set_timestamp(col_date, ndx, Timestamp(i / 4, i % 4)); + } + table.set_null(col_date, 5); + + // Timestamps : {0,0}, {0,1}, {0,2}, {0,3}, {1,0}, {}, {1,2}, {1,3}, {2,0}, {2,1} + + auto timestamps = table.column(col_date); + + CHECK_EQUAL((timestamps > Timestamp(0, 3)).count(), 5); + CHECK_EQUAL((timestamps >= Timestamp(0, 3)).count(), 6); + CHECK_EQUAL((timestamps < Timestamp(1, 3)).count(), 6); + CHECK_EQUAL((timestamps <= Timestamp(1, 3)).count(), 7); + CHECK_EQUAL((timestamps == Timestamp()).count(), 1); + CHECK_EQUAL((timestamps != Timestamp()).count(), 9); +} + TEST(Query_Timestamp_Null) { // Test that querying for null on non-nullable column (with default value being non-null value) is diff --git a/test/test_shared.cpp b/test/test_shared.cpp index 04f69f6333d..9fe630f2450 100644 --- a/test/test_shared.cpp +++ b/test/test_shared.cpp @@ -4007,80 +4007,32 @@ TEST(Shared_GetCommitSize) CHECK_LESS(size_after - size_before, commit_size); } } + /* +#include TEST(Shared_TimestampQuery) { - SHARED_GROUP_TEST_PATH(path); - SharedGroup sg(path); - - { - WriteTransaction wt(sg); - - auto table = wt.get_or_add_table("table"); - auto col_date = table->add_column(type_Timestamp, "date"); - auto col_value = table->add_column(type_Int, "value"); + Table table; + auto col_date = table.add_column(type_Timestamp, "date", true); - for (int i = 0; i < 10; i++) { - auto ndx = table->add_empty_row(); - table->set_timestamp(col_date, ndx, Timestamp(i / 4, i % 4)); - table->set_int(col_value, ndx, i); - } - // Timestamps : {0,0}, {0,1}, {0,2}, {0,3}, {1,0}, {1,1}, {1,2}, {1,3}, {2,0}, {2,1} - wt.commit(); - } - - { - Group& g = const_cast(sg.begin_read()); - auto table = g.get_table("table"); - auto col_date = table->get_column_index("date"); - - Query q = table->column(col_date) > Timestamp(0, 3); - auto cnt = q.count(); - CHECK_EQUAL(cnt, 6); - q = table->column(col_date) >= Timestamp(0, 3); - cnt = q.count(); - CHECK_EQUAL(cnt, 7); - q = table->column(col_date) > Timestamp(0, 3) && - table->column(col_date) < Timestamp(1, 3); - cnt = q.count(); - CHECK_EQUAL(cnt, 3); - sg.end_read(); - } - - { - WriteTransaction wt(sg); - - auto table = wt.get_table("table"); - auto col_date = table->get_column_index("date"); - auto col_value = table->get_column_index("value"); - - table->clear(); - Random random(random_int()); // Seed from slow global generator + Random random(random_int()); // Seed from slow global generator - for (int i = 0; i < 100000; i++) { - auto ndx = table->add_empty_row(); - int seconds = random.draw_int_max(3600 * 24 * 10); - table->set_timestamp(col_date, ndx, Timestamp(seconds, 0)); - table->set_int(col_value, ndx, i); - } - wt.commit(); + for (int i = 0; i < 10000; i++) { + auto ndx = table.add_empty_row(); + int seconds = random.draw_int_max(3600 * 24 * 10); + table.set_timestamp(col_date, ndx, Timestamp(seconds, 0)); } - { - Group& g = const_cast(sg.begin_read()); - auto table = g.get_table("table"); - auto col_date = table->get_column_index("date"); + Query q = table.column(col_date) > Timestamp(3600 * 24 * 5, 3); + auto start = std::chrono::steady_clock::now(); + CALLGRIND_START_INSTRUMENTATION; + auto cnt = q.count(); + CALLGRIND_STOP_INSTRUMENTATION; + auto end = std::chrono::steady_clock::now(); - Query q = table->column(col_date) > Timestamp(3600 * 24 * 5, 3); - auto start = std::chrono::steady_clock::now(); - auto cnt = q.count(); - auto end = std::chrono::steady_clock::now(); - - std::cout << "Time: " << std::chrono::duration_cast(end - start).count() << " us" - << std::endl; - CHECK_GREATER(cnt, 50000); - sg.end_read(); - } + std::cout << "Time: " << std::chrono::duration_cast(end - start).count() << " us" + << std::endl; + CHECK_GREATER(cnt, 50000); } */