Skip to content

Commit

Permalink
Add GreaterEqual and LessEqual specializations
Browse files Browse the repository at this point in the history
We take the position that you cannot query a timestamp column
using <, >, <= and >= against a null value.
  • Loading branch information
James Stone authored and jedelbo committed Aug 15, 2019
1 parent 98b289e commit 2e70935
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 98 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

----------------------------------------------

Expand Down
76 changes: 76 additions & 0 deletions src/realm/query_engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,7 @@ size_t TimestampNode<Greater>::find_first_local(size_t start, size_t end)

return not_found;
}

template <>
size_t TimestampNode<Less>::find_first_local(size_t start, size_t end)
{
Expand Down Expand Up @@ -752,4 +753,79 @@ size_t TimestampNode<Less>::find_first_local(size_t start, size_t end)

return not_found;
}

template <>
size_t TimestampNode<GreaterEqual>::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<GreaterEqual>(start, end);

if (ret == not_found)
return not_found;

util::Optional<int64_t> 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<LessEqual>::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<LessEqual>(start, end);

if (ret == not_found)
return not_found;

util::Optional<int64_t> 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<Greater>::find_first_local(size_t start, size_t end);
template size_t TimestampNode<Less>::find_first_local(size_t start, size_t end);
template size_t TimestampNode<GreaterEqual>::find_first_local(size_t start, size_t end);
template size_t TimestampNode<LessEqual>::find_first_local(size_t start, size_t end);
#endif
} // namespace realm
11 changes: 11 additions & 0 deletions src/realm/query_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1452,6 +1452,17 @@ class TimestampNode : public TimestampNodeBase {
}
};

template <>
size_t TimestampNode<Greater>::find_first_local(size_t start, size_t end);
template <>
size_t TimestampNode<Less>::find_first_local(size_t start, size_t end);
template <>
size_t TimestampNode<GreaterEqual>::find_first_local(size_t start, size_t end);
template <>
size_t TimestampNode<LessEqual>::find_first_local(size_t start, size_t end);



class StringNodeBase : public ParentNode {
public:
using TConditionValue = StringData;
Expand Down
64 changes: 32 additions & 32 deletions test/benchmark-common-tasks/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1233,38 +1233,38 @@ int benchmark_common_tasks_main()

#define BENCH(B) run_benchmark<B>(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);
Expand Down
22 changes: 22 additions & 0 deletions test/test_query.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Timestamp>(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
Expand Down
84 changes: 18 additions & 66 deletions test/test_shared.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4007,80 +4007,32 @@ TEST(Shared_GetCommitSize)
CHECK_LESS(size_after - size_before, commit_size);
}
}

/*
#include <valgrind/callgrind.h>
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<Group&>(sg.begin_read());
auto table = g.get_table("table");
auto col_date = table->get_column_index("date");
Query q = table->column<Timestamp>(col_date) > Timestamp(0, 3);
auto cnt = q.count();
CHECK_EQUAL(cnt, 6);
q = table->column<Timestamp>(col_date) >= Timestamp(0, 3);
cnt = q.count();
CHECK_EQUAL(cnt, 7);
q = table->column<Timestamp>(col_date) > Timestamp(0, 3) &&
table->column<Timestamp>(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<unsigned long>()); // Seed from slow global generator
Random random(random_int<unsigned long>()); // 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<Group&>(sg.begin_read());
auto table = g.get_table("table");
auto col_date = table->get_column_index("date");
Query q = table.column<Timestamp>(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<Timestamp>(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<std::chrono::microseconds>(end - start).count() << " us"
<< std::endl;
CHECK_GREATER(cnt, 50000);
sg.end_read();
}
std::cout << "Time: " << std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() << " us"
<< std::endl;
CHECK_GREATER(cnt, 50000);
}
*/

Expand Down

0 comments on commit 2e70935

Please sign in to comment.