Skip to content

Commit

Permalink
Add MQL translation skeleton
Browse files Browse the repository at this point in the history
  • Loading branch information
jsflax committed Nov 9, 2023
1 parent 7556b53 commit 38bd85e
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 2 deletions.
22 changes: 21 additions & 1 deletion src/realm/object-store/results.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#include <realm/object-store/schema.hpp>
#include <realm/object-store/class.hpp>
#include <realm/object-store/sectioned_results.hpp>

#include <realm/object-store/util/bson/bson.hpp>
#include <realm/set.hpp>

#include <stdexcept>
Expand Down Expand Up @@ -56,6 +56,16 @@ Results::Results(SharedRealm r, Query q, DescriptorOrdering o)
{
}

Results::Results(SharedRealm r, ConstTableRef table, const bson::BsonDocument& document)
: Results(r, Query(table, document))
{
}

Results::Results(SharedRealm r, ConstTableRef table, const std::string& document)
: Results(r, Query(table, static_cast<bson::BsonDocument>(bson::parse(document))))
{
}

Results::Results(const Class& cls)
: Results(cls.get_realm(), cls.get_table())
{
Expand Down Expand Up @@ -924,6 +934,16 @@ Results Results::filter(Query&& q) const
return Results(m_realm, get_query().and_query(std::move(q)), m_descriptor_ordering);
}

Results Results::find(const bson::BsonDocument& document) const
{
return filter(Query(m_table, document));
}

Results Results::find(const std::string& document) const
{
return find(static_cast<bson::BsonDocument>(bson::parse(document)));
}

Results Results::limit(size_t max_count) const
{
util::CheckedUniqueLock lock(m_mutex);
Expand Down
5 changes: 5 additions & 0 deletions src/realm/object-store/results.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ class Results {
Results();
Results(const Class&);
Results(std::shared_ptr<Realm> r, ConstTableRef table);
Results(std::shared_ptr<Realm> r, ConstTableRef, const bson::BsonDocument& document);
Results(std::shared_ptr<Realm> r, ConstTableRef, const std::string& document);
Results(std::shared_ptr<Realm> r, Query q, DescriptorOrdering o = {});
Results(std::shared_ptr<Realm> r, TableView tv, DescriptorOrdering o = {});
Results(std::shared_ptr<Realm> r, const Obj& obj, TableKey src_table, ColKey src_col_key)
Expand Down Expand Up @@ -152,6 +154,9 @@ class Results {

// Create a new Results by further filtering or sorting this Results
Results filter(Query&& q) const REQUIRES(!m_mutex);
Results find(const bson::BsonDocument& document) const REQUIRES(!m_mutex);
Results find(const std::string& document) const REQUIRES(!m_mutex);

// Create a new Results by sorting this Result.
Results sort(SortDescriptor&& sort) const REQUIRES(!m_mutex);
// Create a new Results by sorting this Result based on the specified key paths.
Expand Down
130 changes: 130 additions & 0 deletions src/realm/query.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#include <realm/query_expression.hpp>
#include <realm/table_view.hpp>
#include <realm/set.hpp>
#include <realm/object-store/util/bson/bson.hpp>
#include <realm/object-store/util/bson/indexed_map.hpp>

#include <algorithm>

Expand Down Expand Up @@ -74,6 +76,13 @@ Query::Query(ConstTableRef table, std::unique_ptr<TableView> tv)
create();
}

Query::Query(ConstTableRef table, const bson::BsonDocument& document)
: m_table(table.cast_away_const())
{
create();
translate_from(document);
}

void Query::create()
{
if (m_table && m_table->is_asymmetric()) {
Expand Down Expand Up @@ -1935,3 +1944,124 @@ QueryGroup& QueryGroup::operator=(const QueryGroup& other)
}
return *this;
}

enum class QueryComparisonOperators {
$eq, $gt, $gte, $in, $lt, $lte, $ne, $nin
};
std::unordered_map<std::string, QueryComparisonOperators> comparison_operators = {
std::pair { "$eq", QueryComparisonOperators::$eq },
std::pair { "$gt", QueryComparisonOperators::$gt },
std::pair { "$gte", QueryComparisonOperators::$gte },
std::pair { "$in", QueryComparisonOperators::$in },
std::pair { "$lt", QueryComparisonOperators::$lt },
std::pair { "$lte", QueryComparisonOperators::$lte },
std::pair { "$ne", QueryComparisonOperators::$ne },
};
enum class QueryLogicalOperators {
$and, $not, $nor, $or
};
std::unordered_map<std::string, QueryLogicalOperators> logical_operators = {
std::pair { "$and", QueryLogicalOperators::$and },
std::pair { "$or", QueryLogicalOperators::$or },
std::pair { "$nor", QueryLogicalOperators::$nor },
std::pair { "$not", QueryLogicalOperators::$not },
};

template <typename T>
inline Query& add_condition_for_operator(Query& query,
ColKey column_key,
const T& value,
QueryComparisonOperators op) {
// if working with raw BSON, unwrap bson value and pass back in
if constexpr (std::is_same_v<T, bson::Bson>) {
switch (value.type()) {
case bson::Bson::Type::Int32:
return add_condition_for_operator(query, column_key, static_cast<int32_t>(value), op);
case bson::Bson::Type::Int64:
return add_condition_for_operator(query, column_key, static_cast<int64_t>(value), op);
case bson::Bson::Type::Bool:
return add_condition_for_operator(query, column_key, static_cast<bool>(value), op);
case bson::Bson::Type::Double:
return add_condition_for_operator(query, column_key, static_cast<double>(value), op);
case bson::Bson::Type::String:
return add_condition_for_operator(query, column_key, StringData(static_cast<std::string>(value)), op);
case bson::Bson::Type::Binary: {
auto data = static_cast<std::vector<char>>(value);
std::string s(begin(data), end(data));
return add_condition_for_operator(query, column_key, BinaryData(s), op);
}
case bson::Bson::Type::Timestamp:
case bson::Bson::Type::Datetime:
return add_condition_for_operator(query, column_key, static_cast<Timestamp>(value), op);
case bson::Bson::Type::ObjectId:
return add_condition_for_operator(query, column_key, static_cast<ObjectId>(value), op);
case bson::Bson::Type::Decimal128:
return add_condition_for_operator(query, column_key, static_cast<Decimal128>(value), op);
case bson::Bson::Type::Uuid:
return add_condition_for_operator(query, column_key, static_cast<UUID>(value), op);
case bson::Bson::Type::Null:
default:
throw Exception(ErrorCodes::Error::MalformedJson, "Unsupported Bson Type");
}
} else {
switch (op) {
case QueryComparisonOperators::$eq: return query.equal(column_key, value);
case QueryComparisonOperators::$gt: return query.greater(column_key, value);
case QueryComparisonOperators::$gte: return query.greater_equal(column_key, value);
case QueryComparisonOperators::$lt: return query.less(column_key, value);
case QueryComparisonOperators::$lte: return query.less_equal(column_key, value);
case QueryComparisonOperators::$ne: return query.not_equal(column_key, value);
case QueryComparisonOperators::$nin:
case QueryComparisonOperators::$in:
default:
throw Exception(ErrorCodes::Error::MalformedJson, "Unsupported Bson Type");
}
}
}

inline Query& Query::translate_for_column(const bson::BsonDocument& sub_document, realm::ColKey column_key) {
for (const auto &[key, value]: sub_document) {
add_condition_for_operator(*this, column_key, value, comparison_operators[key]);
}
return *this;
}

inline Query& Query::translate_from(const bson::BsonDocument &document) { // NOLINT -misc-no-recursion
for (const auto &[key, value]: document) {
// top level document will contain either keys to compare values against,
// or logical operators like $and or $or that will contain an array of query ops
if (logical_operators.count(key)) {
switch (logical_operators[key]) {
case QueryLogicalOperators::$and: {
auto documents = static_cast<bson::BsonArray>(value);
for (const auto &and_document: documents) {
this->and_query(translate_from(static_cast<bson::BsonDocument>(and_document)));
}
return *this;
}
case QueryLogicalOperators::$not:
return translate_from(static_cast<bson::BsonDocument>(value)).Not();
case QueryLogicalOperators::$nor:
break;
case QueryLogicalOperators::$or: {
auto documents = static_cast<bson::BsonArray>(value);
for (const auto &and_document: documents) {
operator||(translate_from(static_cast<bson::BsonDocument>(and_document)));
}
return *this;
}
default: break;
}
}

auto column_key = m_table->get_column_key(key);
// if the value type is a document, we expect that it is a
// "query document". if it's not, we assume they are doing a value comparison
if (value.type() == bson::Bson::Type::Document) {
translate_for_column(static_cast<bson::BsonDocument>(value), column_key);
} else {
add_condition_for_operator(*this, column_key, value, QueryComparisonOperators::$eq);
}
}
return *this;
}
11 changes: 10 additions & 1 deletion src/realm/query.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@
#include <realm/util/serializer.hpp>

namespace realm {

namespace bson {
class Bson;
template <typename T>
class IndexedMap;
using BsonDocument = IndexedMap<Bson>;
}

// Pre-declarations
class Array;
Expand Down Expand Up @@ -84,6 +89,7 @@ class Query final {
Query(ConstTableRef table, std::unique_ptr<TableView>);
Query(ConstTableRef table, const ObjList& list);
Query(ConstTableRef table, LinkCollectionPtr&& list_ptr);
Query(ConstTableRef table, const bson::BsonDocument& document);
Query();
Query(std::unique_ptr<Expression>);
~Query() noexcept;
Expand Down Expand Up @@ -399,6 +405,9 @@ class Query final {
TableView* m_source_table_view = nullptr; // table views are not refcounted, and not owned by the query.
std::unique_ptr<TableView> m_owned_source_table_view; // <--- except when indicated here
util::bind_ptr<DescriptorOrdering> m_ordering;

inline Query& translate_from(const bson::BsonDocument&);
inline Query& translate_for_column(const bson::BsonDocument& sub_document, ColKey col_key);
};

// Implementation:
Expand Down
105 changes: 105 additions & 0 deletions test/object-store/results.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5016,6 +5016,111 @@ TEST_CASE("results: public name declared", "[results]") {
}
}

TEST_CASE("results: bson constructor", "[results]") {
InMemoryTestFile config;
// config.cache = false;
config.automatic_change_notifications = false;
config.schema = Schema{
{"object",
{
{"int_col", PropertyType::Int},
{"str_col", PropertyType::String},
}},
};

auto realm = Realm::get_shared_realm(config);
auto table = realm->read_group().get_table("class_object");
auto int_col = table->get_column_key("int_col");
auto str_col = table->get_column_key("str_col");
realm->begin_transaction();
for (int i = 0; i < 8; ++i) {
auto obj = table->create_object();
obj.set(int_col, (i + 2) % 4);
obj.set(str_col, "hello");
}
realm->commit_transaction();
Results r(realm, table);

SECTION("test no operator equals") {
auto object_results = r.find(R""""(
{
"int_col": 0
}
)"""");
CHECK(object_results.size() == 2);
object_results = r.find(R""""(
{
"int_col": 1
}
)"""");
CHECK(object_results.size() == 2);
object_results = r.find(R""""(
{
"str_col": "hello"
}
)"""");
CHECK(object_results.size() == 8);
object_results = r.find(R""""(
{
"str_col": "bye"
}
)"""");
CHECK(object_results.size() == 0);
}
SECTION("test int comparison operators") {
auto object_results = r.find(R""""(
{
"int_col": { "$eq": 1 }
}
)"""");
CHECK(object_results.size() == 2);
object_results = r.find(R""""(
{
"int_col": { "$gt": 2 }
}
)"""");
CHECK(object_results.size() == 2);
object_results = r.find(R""""(
{
"int_col": { "$gte": 2 }
}
)"""");
CHECK(object_results.size() == 4);
object_results = r.find(R""""(
{
"int_col": { "$lt": 2 }
}
)"""");
CHECK(object_results.size() == 4);
object_results = r.find(R""""(
{
"int_col": { "$lte": 2 }
}
)"""");
CHECK(object_results.size() == 6);
}
SECTION("test logical AND operator") {
auto object_results = r.find(R""""(
{
"$and": [
{ "int_col": { "$gt": 2 } },
{ "str_col": { "$eq": "hello" } }
]
}
)"""");
CHECK(object_results.size() == 2);
object_results = r.find(R""""(
{
"$and": [
{ "int_col": { "$gt": 2 } },
{ "str_col": { "$eq": "bye" } }
]
}
)"""");
CHECK(object_results.size() == 0);
}
}

TEST_CASE("notifications: objects with PK recreated", "[results]") {
_impl::RealmCoordinator::assert_no_open_realms();
InMemoryTestFile config;
Expand Down

0 comments on commit 38bd85e

Please sign in to comment.