diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 0d1ee301..9defdb9f 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -52,7 +52,7 @@ jobs: - name: Setup Ccache uses: hendrikmuhs/ccache-action@v1.1 with: - key: ccache-macos-${{ matrix.xcode }} + key: ccache-macos-${{ matrix.xcode }}-disable-aligned-storage-${{ matrix.disable-aligned-storage }}-${{ matrix.configuration }} - name: Setup Ninja uses: seanmiddleditch/gha-setup-ninja@master @@ -137,7 +137,7 @@ jobs: - name: Setup Ccache uses: hendrikmuhs/ccache-action@v1.1 with: - key: ccache-linux-${{ matrix.compiler.name }}-${{ matrix.compiler.version }} + key: ccache-linux-${{ matrix.compiler.name }}-${{ matrix.compiler.version }}-${{ matrix.configuration }} - name: Setup CMake uses: jwlawson/actions-setup-cmake@v1.13 diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fd901c1..6b0a02cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +NEXT-RELEASE Release notes (YYYY-MM-DD) +============================================================= + +### Fixed +* Using the `==` operator in a type safe query for a nullable string property would return the incorrect result when algined storage was disabled. + +### Enhancements +* Add ability to use `managed>` in type safe queries when comparing a value for a key. e.g. + `realm.object().where([](auto& o) { return o.my_map["foo_key"] == "some value"; })` + Supported operators are `==`, `!=`, `>`, `<`, `>=`, `<=` and `contains(const std::string&)`. + +### Compatibility +* Fileformat: Generates files with format v24. Reads and automatically upgrade from fileformat v10. + +### Internals +* None + 2.1.0 Release notes (2024-06-27) ============================================================= diff --git a/include/cpprealm/internal/bridge/query.hpp b/include/cpprealm/internal/bridge/query.hpp index 569c91ba..de8b4c7b 100644 --- a/include/cpprealm/internal/bridge/query.hpp +++ b/include/cpprealm/internal/bridge/query.hpp @@ -232,6 +232,15 @@ namespace realm::internal::bridge { query& links_to(col_key column_key, const internal::bridge::obj& o); query& not_links_to(col_key column_key, const internal::bridge::obj& o); + query& dictionary_has_value_for_key_equals(col_key column_key, const std::string& key, const mixed& value); + query& dictionary_has_value_for_key_not_equals(col_key column_key, const std::string& key, const mixed& value); + query& dictionary_has_value_for_key_greater_than(col_key column_key, const std::string& key, const mixed& value); + query& dictionary_has_value_for_key_less_than(col_key column_key, const std::string& key, const mixed& value); + query& dictionary_has_value_for_key_greater_than_equals(col_key column_key, const std::string& key, const mixed& value); + query& dictionary_has_value_for_key_less_than_equals(col_key column_key, const std::string& key, const mixed& value); + query& dictionary_contains_string_for_key(col_key column_key, const std::string& key, const std::string& value); + subexpr dictionary_link_subexpr(col_key column_key, col_key link_column_key, const std::string& key); + // Expressions static query falsepredicate(); diff --git a/include/cpprealm/managed_dictionary.hpp b/include/cpprealm/managed_dictionary.hpp index 7a14f6b4..f272bc67 100644 --- a/include/cpprealm/managed_dictionary.hpp +++ b/include/cpprealm/managed_dictionary.hpp @@ -23,6 +23,7 @@ #include #include #include +#include namespace realm { @@ -33,6 +34,16 @@ namespace realm { const internal::bridge::realm &r) : m_backing_map(std::move(backing_map)), m_key(key), m_realm(r) {} + box_base(rbool* query, + internal::bridge::col_key column_key, + const std::string &key, + const internal::bridge::realm &r) { + m_rbool_query = query; + m_col_key = column_key; + m_key = key; + m_realm = r; + } + box_base &operator=(const mapped_type &o) { m_backing_map.insert(m_key, internal::bridge::mixed(std::move(o))); return *this; @@ -86,25 +97,33 @@ namespace realm { return *this; } - bool operator==(const mapped_type &rhs) const { - if constexpr (internal::type_info::is_optional::value) { - auto v = m_backing_map.get(m_key); - if (v.is_null() && !rhs) { - return true; + rbool operator==(const mapped_type &rhs) const { + if constexpr (realm::internal::type_info::MixedPersistableConcept::value) { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_equals(this->m_col_key, m_key, serialize(rhs, m_realm)); } - return m_backing_map.get(m_key) == rhs; - } else if constexpr (realm::internal::type_info::MixedPersistableConcept::value) { return m_backing_map.get(m_key) == serialize(rhs, m_realm); } else { - return m_backing_map.get(m_key) == internal::bridge::mixed(rhs); + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_equals(this->m_col_key, m_key, internal::bridge::mixed(serialize(rhs))); + } + return m_backing_map.get(m_key) == internal::bridge::mixed(serialize(rhs)); } } - bool operator!=(const mapped_type &rhs) const { - return !this->operator==(rhs); + + rbool operator!=(const mapped_type &rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_not_equals(this->m_col_key, m_key, internal::bridge::mixed(serialize(rhs, m_realm))); + } else { + return !operator==(rhs); + } } + internal::bridge::core_dictionary m_backing_map; internal::bridge::realm m_realm; std::string m_key; + internal::bridge::col_key m_col_key; + rbool* m_rbool_query = nullptr; }; template struct box; @@ -115,6 +134,34 @@ namespace realm { int64_t operator*() { return m_backing_map.get(m_key).operator int64_t(); } + + rbool operator>(int64_t rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_greater_than(this->m_col_key, m_key, rhs); + } + return m_backing_map.get(m_key) > internal::bridge::mixed(rhs); + } + + rbool operator>=(int64_t rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_greater_than_equals(this->m_col_key, m_key, rhs); + } + return m_backing_map.get(m_key) >= internal::bridge::mixed(rhs); + } + + rbool operator<(int64_t rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_less_than(this->m_col_key, m_key, rhs); + } + return m_backing_map.get(m_key) < internal::bridge::mixed(rhs); + } + + rbool operator<=(int64_t rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_less_than_equals(this->m_col_key, m_key, rhs); + } + return m_backing_map.get(m_key) <= internal::bridge::mixed(rhs); + } }; template<> struct box : public box_base { @@ -123,6 +170,34 @@ namespace realm { double operator*() { return m_backing_map.get(m_key).operator double(); } + + rbool operator>(double rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_greater_than(this->m_col_key, m_key, rhs); + } + return m_backing_map.get(m_key) > internal::bridge::mixed(rhs); + } + + rbool operator>=(double rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_greater_than_equals(this->m_col_key, m_key, rhs); + } + return m_backing_map.get(m_key) >= internal::bridge::mixed(rhs); + } + + rbool operator<(double rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_less_than(this->m_col_key, m_key, rhs); + } + return m_backing_map.get(m_key) < internal::bridge::mixed(rhs); + } + + rbool operator<=(double rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_less_than_equals(this->m_col_key, m_key, rhs); + } + return m_backing_map.get(m_key) <= internal::bridge::mixed(rhs); + } }; template<> struct box : public box_base { @@ -139,39 +214,8 @@ namespace realm { V operator*() { return this->m_backing_map.get(this->m_key).operator int64_t(); } - - bool operator==(const V& rhs) const { - return this->m_backing_map.get(this->m_key).operator int64_t() == static_cast(rhs); - } - bool operator!=(const V& rhs) const { - return !this->operator==(rhs); - } }; - template - struct box>> : public box_base { - using box_base::box_base; - using box_base::operator=; - V operator*() { - auto v = this->m_backing_map.get(this->m_key); - if (v.is_null()) { - return std::nullopt; - } else { - return static_cast(v.operator int64_t()); - }; - } - - bool operator==(const V& rhs) const { - auto v = this->m_backing_map.get(this->m_key); - if (v.is_null() && !rhs) { - return true; - } - return static_cast(v.operator int64_t()) == rhs; - } - bool operator!=(const V& rhs) const { - return !this->operator==(rhs); - } - }; template<> struct box : public box_base { using box_base::box_base; @@ -184,6 +228,34 @@ namespace realm { struct box::value>> : public box_base { using box_base::box_base; using box_base::operator=; + + rbool operator>(Mixed rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_greater_than(this->m_col_key, this->m_key, serialize(rhs, this->m_realm)); + } + return this->m_backing_map.get(this->m_key) > serialize(rhs, this->m_realm); + } + + rbool operator>=(Mixed rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_greater_than_equals(this->m_col_key, this->m_key, serialize(rhs, this->m_realm)); + } + return this->m_backing_map.get(this->m_key) >= serialize(rhs, this->m_realm); + } + + rbool operator<(Mixed rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_less_than(this->m_col_key, this->m_key, serialize(rhs, this->m_realm)); + } + return this->m_backing_map.get(this->m_key) < serialize(rhs, this->m_realm); + } + + rbool operator<=(Mixed rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_less_than_equals(this->m_col_key, this->m_key, serialize(rhs, this->m_realm)); + } + return this->m_backing_map.get(this->m_key) <= serialize(rhs, this->m_realm); + } }; template<> struct box : public box_base { @@ -200,6 +272,34 @@ namespace realm { decimal128 operator*() { return this->m_backing_map.get(this->m_key).operator internal::bridge::decimal128().operator ::realm::decimal128(); } + + rbool operator>(decimal128 rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_greater_than(this->m_col_key, m_key, serialize(rhs)); + } + return m_backing_map.get(m_key) > internal::bridge::mixed(serialize(rhs)); + } + + rbool operator>=(decimal128 rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_greater_than_equals(this->m_col_key, m_key, serialize(rhs)); + } + return m_backing_map.get(m_key) >= internal::bridge::mixed(serialize(rhs)); + } + + rbool operator<(decimal128 rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_less_than(this->m_col_key, m_key, serialize(rhs)); + } + return m_backing_map.get(m_key) < internal::bridge::mixed(serialize(rhs)); + } + + rbool operator<=(decimal128 rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_less_than_equals(this->m_col_key, m_key, serialize(rhs)); + } + return m_backing_map.get(m_key) <= internal::bridge::mixed(serialize(rhs)); + } }; template<> struct box> : public box_base> { @@ -208,6 +308,34 @@ namespace realm { std::chrono::time_point operator*() { return this->m_backing_map.get(this->m_key).operator internal::bridge::timestamp().operator std::chrono::time_point(); } + + rbool operator>(std::chrono::time_point rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_greater_than(this->m_col_key, m_key, serialize(rhs)); + } + return m_backing_map.get(m_key) > internal::bridge::mixed(rhs); + } + + rbool operator>=(std::chrono::time_point rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_greater_than_equals(this->m_col_key, m_key, serialize(rhs)); + } + return m_backing_map.get(m_key) >= internal::bridge::mixed(rhs); + } + + rbool operator<(std::chrono::time_point rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_less_than(this->m_col_key, m_key, serialize(rhs)); + } + return m_backing_map.get(m_key) < internal::bridge::mixed(rhs); + } + + rbool operator<=(std::chrono::time_point rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_less_than_equals(this->m_col_key, m_key, serialize(rhs)); + } + return m_backing_map.get(m_key) <= internal::bridge::mixed(rhs); + } }; template<> struct box> : public box_base> { @@ -224,6 +352,14 @@ namespace realm { std::string operator*() { return this->m_backing_map.get(this->m_key).operator std::string(); } + + rbool contains(const std::string& rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_contains_string_for_key(this->m_col_key, m_key, rhs); + } + std::string lhs = m_backing_map.get(m_key); + return lhs.find(rhs) != std::string::npos; + } }; //MARK: - Boxed Link @@ -233,19 +369,12 @@ namespace realm { using box_base>::operator=; using box_base>::operator==; - bool operator==(const V*& rhs) const { - auto a = const_cast *>(this)->m_backing_map.get_object(this->m_key); - auto &b = rhs->m_managed.m_obj; - if (this->m_realm != rhs->m_managed.m_realm) { - return false; + rbool operator==(const managed &rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_equals(this->m_col_key, + this->m_key, + rhs ? internal::bridge::mixed(internal::bridge::obj_link(rhs.m_obj->get_table().get_key(), rhs.m_obj->get_key())) : internal::bridge::mixed()); } - return a.get_table() == b.get_table() && a.get_key() == b.get_key(); - } - bool operator!=(const V*& rhs) const { - return !this->operator==(rhs); - } - - bool operator==(const managed &rhs) const { auto a = const_cast> *>(this)->m_backing_map.get_object(this->m_key); auto &b = rhs.m_obj; if (this->m_realm != *rhs.m_realm) { @@ -253,11 +382,23 @@ namespace realm { } return a.get_key() == b->get_key(); } - bool operator!=(const managed rhs) const { + + rbool operator!=(const managed rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_not_equals(this->m_col_key, + this->m_key, + rhs ? internal::bridge::mixed(internal::bridge::obj_link(rhs.m_obj->get_table().get_key(), rhs.m_obj->get_key())) : internal::bridge::mixed()); + } return !this->operator==(rhs); } - bool operator==(const managed &rhs) const { + rbool operator==(const managed &rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_equals(this->m_col_key, + this->m_key, + internal::bridge::obj_link(rhs.m_obj.get_table().get_key(), rhs.m_obj.get_key())); + } + auto a = const_cast> *>(this)->m_backing_map.get_object(this->m_key); auto &b = rhs.m_obj; if (this->m_realm != rhs.m_realm) { @@ -265,7 +406,14 @@ namespace realm { } return a.get_key() == b.get_key(); } - bool operator!=(const managed rhs) const { + + rbool operator!=(const managed rhs) const { + if (this->m_rbool_query) { + return this->m_rbool_query->dictionary_has_value_for_key_not_equals(this->m_col_key, + this->m_key, + internal::bridge::obj_link(rhs.m_obj.get_table().get_key(), rhs.m_obj.get_key())); + } + return !this->operator==(rhs); } @@ -278,6 +426,13 @@ namespace realm { } typename managed::ref_type operator->() { + if (this->m_rbool_query) { + rbool::dictionary_context ctx; + ctx.m_key = this->m_key; + ctx.origin_col_key = this->m_col_key; + this->m_rbool_query->add_dictionary_link_chain(std::move(ctx)); + return typename managed::ref_type(managed::prepare_for_query(this->m_realm, this->m_rbool_query)); + } auto obj = this->m_backing_map.get_object(this->m_key); return typename managed::ref_type(managed(std::move(obj), this->m_realm)); } @@ -322,6 +477,7 @@ namespace realm { } return a.get_key() == b.get_key(); } + bool operator!=(const box> rhs) const { return !this->operator==(rhs); } @@ -334,6 +490,7 @@ namespace realm { } return a.get_key() == b.get_key(); } + bool operator!=(const box& rhs) const { return !this->operator==(rhs); } @@ -452,11 +609,17 @@ namespace realm { } } - box, managed, T>> operator[](const std::string &a) { + box, managed, T>> operator[](const std::string &key) { if constexpr (std::is_pointer_v) { - return box>(m_obj->get_dictionary(m_key), a, *m_realm); + if (m_rbool_query) { + return box>(m_rbool_query, m_key, key, *m_realm); + } + return box>(m_obj->get_dictionary(m_key), key, *m_realm); } else { - return box(m_obj->get_dictionary(m_key), a, *m_realm); + if (m_rbool_query) { + return box(m_rbool_query, m_key, key, *m_realm); + } + return box(m_obj->get_dictionary(m_key), key, *m_realm); } } @@ -464,7 +627,6 @@ namespace realm { m_obj->get_dictionary(m_key).erase(key); } - notification_token observe(std::function&& fn) { auto o = internal::bridge::object(*m_realm, *m_obj); diff --git a/include/cpprealm/rbool.hpp b/include/cpprealm/rbool.hpp index ecfe39c8..1a659872 100644 --- a/include/cpprealm/rbool.hpp +++ b/include/cpprealm/rbool.hpp @@ -34,15 +34,12 @@ namespace realm { // MARK: rbool class rbool { - bool is_for_queries = false; - std::optional m_link_chain; - internal::bridge::table m_table; - - template - friend struct results; - friend rbool operator&&(const rbool &lhs, const rbool &rhs); - friend rbool operator||(const rbool &lhs, const rbool &rhs); public: + struct dictionary_context { + internal::bridge::col_key origin_col_key; + std::string m_key; + }; + rbool& add_link_chain(const internal::bridge::col_key& col_key) { if (m_link_chain) { m_link_chain->link(col_key); @@ -57,6 +54,9 @@ namespace realm { if (auto lc = m_link_chain) { \ q = lc->column(col_key).comparison(rhs); \ m_link_chain = std::nullopt; \ + } else if (m_dictionary_ctx) { \ + q = q.dictionary_link_subexpr(m_dictionary_ctx->origin_col_key, col_key, m_dictionary_ctx->m_key).comparison(rhs); \ + m_dictionary_ctx = std::nullopt; \ } else { \ if (rhs) { \ q = internal::bridge::query(q.get_table()).comparison(col_key, *rhs); \ @@ -70,6 +70,9 @@ namespace realm { if (auto lc = m_link_chain) { \ q = lc->column(col_key).comparison(std::optional(rhs)); \ m_link_chain = std::nullopt; \ + } else if (m_dictionary_ctx) { \ + q = q.dictionary_link_subexpr(m_dictionary_ctx->origin_col_key, col_key, m_dictionary_ctx->m_key).comparison(std::optional(rhs)); \ + m_dictionary_ctx = std::nullopt; \ } else { \ q = internal::bridge::query(q.get_table()).comparison(col_key, rhs); \ } \ @@ -81,6 +84,9 @@ namespace realm { if (auto lc = m_link_chain) { \ q = lc->column(col_key).comparison(::realm::serialize(std::optional(rhs))); \ m_link_chain = std::nullopt; \ + } else if (m_dictionary_ctx) { \ + q = q.dictionary_link_subexpr(m_dictionary_ctx->origin_col_key, col_key, m_dictionary_ctx->m_key).comparison(std::optional(rhs)); \ + m_dictionary_ctx = std::nullopt; \ } else { \ q = internal::bridge::query(q.get_table()).comparison(col_key, rhs); \ } \ @@ -188,6 +194,48 @@ namespace realm { return *this; } + // Dictionary + + rbool& dictionary_has_value_for_key_equals(internal::bridge::col_key column_key, const std::string& key, const internal::bridge::mixed& value) { + q = internal::bridge::query(q.get_table()).dictionary_has_value_for_key_equals(column_key, key, value); + return *this; + } + + rbool& dictionary_has_value_for_key_not_equals(internal::bridge::col_key column_key, const std::string& key, const internal::bridge::mixed& value) { + q = internal::bridge::query(q.get_table()).dictionary_has_value_for_key_not_equals(column_key, key, value); + return *this; + } + + rbool& dictionary_has_value_for_key_greater_than(internal::bridge::col_key column_key, const std::string& key, const internal::bridge::mixed& value) { + q = internal::bridge::query(q.get_table()).dictionary_has_value_for_key_greater_than(column_key, key, value); + return *this; + } + + rbool& dictionary_has_value_for_key_less_than(internal::bridge::col_key column_key, const std::string& key, const internal::bridge::mixed& value) { + q = internal::bridge::query(q.get_table()).dictionary_has_value_for_key_less_than(column_key, key, value); + return *this; + } + + rbool& dictionary_has_value_for_key_greater_than_equals(internal::bridge::col_key column_key, const std::string& key, const internal::bridge::mixed& value) { + q = internal::bridge::query(q.get_table()).dictionary_has_value_for_key_greater_than_equals(column_key, key, value); + return *this; + } + + rbool& dictionary_has_value_for_key_less_than_equals(internal::bridge::col_key column_key, const std::string& key, const internal::bridge::mixed& value) { + q = internal::bridge::query(q.get_table()).dictionary_has_value_for_key_less_than_equals(column_key, key, value); + return *this; + } + + rbool& dictionary_contains_string_for_key(internal::bridge::col_key column_key, const std::string& key, const std::string& value) { + q = internal::bridge::query(q.get_table()).dictionary_contains_string_for_key(column_key, key, value); + return *this; + } + + rbool& add_dictionary_link_chain(dictionary_context&& ctx) { + m_dictionary_ctx = ctx; + return *this; + } + ~rbool() { if (is_for_queries) q.~query(); @@ -207,7 +255,7 @@ namespace realm { mutable internal::bridge::query q; }; - rbool(internal::bridge::query &&q) : is_for_queries(true), q(q) { + rbool(internal::bridge::query &&q) : q(q), is_for_queries(true) { m_table = q.get_table(); } rbool(bool b) : b(b) {} @@ -218,6 +266,18 @@ namespace realm { } else b = r.b; } + + private: + bool is_for_queries = false; + bool is_dictionary_link = false; + std::optional m_link_chain; + internal::bridge::table m_table; + std::optional m_dictionary_ctx; + + template + friend struct results; + friend rbool operator&&(const rbool &lhs, const rbool &rhs); + friend rbool operator||(const rbool &lhs, const rbool &rhs); }; inline rbool operator &&(const rbool& lhs, const rbool& rhs) { @@ -247,7 +307,7 @@ namespace realm { return rbool(table); } -/// Return no objects from a collection. + /// Return no objects from a collection. template inline rbool falsepredicate(const T& o) { rbool* rb = internal::get_rbool(o); diff --git a/src/cpprealm/internal/bridge/query.cpp b/src/cpprealm/internal/bridge/query.cpp index 4500300c..e0b7a934 100644 --- a/src/cpprealm/internal/bridge/query.cpp +++ b/src/cpprealm/internal/bridge/query.cpp @@ -255,7 +255,7 @@ namespace realm::internal::bridge { if (rhs) { return *std::dynamic_pointer_cast<::realm::Columns>(m_subexpr) == *rhs; } else { - return *std::dynamic_pointer_cast<::realm::Columns>(m_subexpr) >= ::realm::null(); + return *std::dynamic_pointer_cast<::realm::Columns>(m_subexpr) == ::realm::null(); } } @@ -550,7 +550,6 @@ namespace realm::internal::bridge { __generate_query_operator(equal, bool) __generate_query_operator(not_equal, bool) - query& links_to(col_key column_key, bool value); query& query::links_to(col_key column_key, const internal::bridge::obj& o) { #ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES *reinterpret_cast(&m_query) = reinterpret_cast(&m_query)->links_to(column_key, o.get_key()); @@ -607,6 +606,108 @@ namespace realm::internal::bridge { #endif } + // Dictionary + + query& query::dictionary_has_value_for_key_equals(col_key column_key, const std::string& key, const mixed& value) { +#ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES + *reinterpret_cast(&m_query) = reinterpret_cast(&m_query)->get_table()->column(column_key.operator ColKey()).key(key) == value.operator Mixed(); +#else + m_query = std::make_shared(m_query->get_table()->column(column_key.operator ColKey()).key(key) == value.operator Mixed()); +#endif + return *this; + } + + query& query::dictionary_has_value_for_key_not_equals(col_key column_key, const std::string& key, const mixed& value) { +#ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES + *reinterpret_cast(&m_query) = reinterpret_cast(&m_query)->get_table()->column(column_key.operator ColKey()).key(key) != value.operator Mixed(); +#else + m_query = std::make_shared(m_query->get_table()->column(column_key.operator ColKey()).key(key) != value.operator Mixed()); +#endif + return *this; + } + + query& query::dictionary_has_value_for_key_greater_than(col_key column_key, const std::string& key, const mixed& value) { +#ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES + *reinterpret_cast(&m_query) = reinterpret_cast(&m_query)->get_table()->column(column_key.operator ColKey()).key(key) > value.operator Mixed(); +#else + m_query = std::make_shared(m_query->get_table()->column(column_key.operator ColKey()).key(key) > value.operator Mixed()); +#endif + return *this; + } + + query& query::dictionary_has_value_for_key_less_than(col_key column_key, const std::string& key, const mixed& value) { +#ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES + *reinterpret_cast(&m_query) = reinterpret_cast(&m_query)->get_table()->column(column_key.operator ColKey()).key(key) < value.operator Mixed(); +#else + m_query = std::make_shared(m_query->get_table()->column(column_key.operator ColKey()).key(key) < value.operator Mixed()); +#endif + return *this; + } + + query& query::dictionary_has_value_for_key_greater_than_equals(col_key column_key, const std::string& key, const mixed& value) { +#ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES + *reinterpret_cast(&m_query) = reinterpret_cast(&m_query)->get_table()->column(column_key.operator ColKey()).key(key) >= value.operator Mixed(); +#else + m_query = std::make_shared(m_query->get_table()->column(column_key.operator ColKey()).key(key) >= value.operator Mixed()); +#endif + return *this; + } + + query& query::dictionary_has_value_for_key_less_than_equals(col_key column_key, const std::string& key, const mixed& value) { +#ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES + *reinterpret_cast(&m_query) = reinterpret_cast(&m_query)->get_table()->column(column_key.operator ColKey()).key(key) <= value.operator Mixed(); +#else + m_query = std::make_shared(m_query->get_table()->column(column_key.operator ColKey()).key(key) <= value.operator Mixed()); +#endif + return *this; + } + + query& query::dictionary_contains_string_for_key(col_key column_key, const std::string& key, const std::string& value) { +#ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES + *reinterpret_cast(&m_query) = reinterpret_cast(&m_query)->get_table()->column(column_key.operator ColKey()).key(key).contains(value); +#else + m_query = std::make_shared(m_query->get_table()->column(column_key.operator ColKey()).key(key).contains(value)); +#endif + return *this; + } + + subexpr query::dictionary_link_subexpr(col_key column_key, col_key link_column_key, const std::string& key) { +#ifdef CPPREALM_HAVE_GENERATED_BRIDGE_TYPES + auto table = reinterpret_cast(&m_query)->get_table()->column(column_key.operator ColKey()).key(key).get_target_table(); +#else + auto table = m_query->get_table()->column(column_key.operator ColKey()).key(key).get_target_table(); +#endif + auto col_type = table->get_column_type(link_column_key); + switch (col_type) { + case type_Int: + return table->column(link_column_key).clone(); + case type_Bool: + return table->column(link_column_key).clone(); + case type_String: + return table->column(link_column_key).clone(); + case type_Binary: + return table->column(link_column_key).clone(); + case type_Mixed: + return table->column(link_column_key).clone(); + case type_Timestamp: + return table->column(link_column_key).clone(); + case type_Float: + return table->column(link_column_key).clone(); + case type_Double: + return table->column(link_column_key).clone(); + case type_Decimal: + return table->column(link_column_key).clone(); + case type_Link: + case type_TypedLink: + return table->column(link_column_key).clone(); + case type_ObjectId: + return table->column(link_column_key).clone(); + case type_UUID: + return table->column(link_column_key).clone(); + } + ::REALM_UNREACHABLE(); + } + __generate_string_query_operator_case_sensitive(equal, std::string_view) __generate_string_query_operator_case_sensitive(not_equal, std::string_view) __generate_string_query_operator_case_sensitive(contains, std::string_view) diff --git a/tests/db/query_tests.cpp b/tests/db/query_tests.cpp index 4abe4b5c..b478c7b1 100644 --- a/tests/db/query_tests.cpp +++ b/tests/db/query_tests.cpp @@ -408,5 +408,187 @@ namespace realm { }); CHECK(res.size() == 3); } + + SECTION("map") { + auto realm = db(std::move(config)); + auto date = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + auto obj_id = realm::object_id::generate(); + + auto create_obj = [&](int64_t pk) { + auto obj = AllTypesObject(); + obj._id = pk; + obj.str_col = "root obj"; + auto obj_link = AllTypesObjectLink(); + obj_link._id = pk; + obj_link.str_col = "foo"; + auto obj_link2 = StringObject(); + obj_link2._id = pk; + obj_link2.str_col = "bar"; + obj_link.str_link_col = &obj_link2; + + auto embedded_obj = AllTypesObjectEmbedded(); + embedded_obj.str_col = "foo_embedded"; + + obj.map_int_col["one"] = 1; + obj.map_int_col["two"] = 2; + + obj.map_bool_col["is_true"] = true; + + obj.map_double_col["my_double"] = 1.234; + obj.map_double_col["my_double2"] = 2.234; + + obj.map_str_col["foo"] = "bar"; + obj.map_str_col["name"] = "Fido"; + + obj.map_uuid_col["my_uuid"] = realm::uuid("18de7916-7f84-11ec-a8a3-0242ac120002"); + obj.map_object_id_col["my_object_id"] = obj_id; + + obj.map_decimal_col["my_decimal"] = realm::decimal128(1.234); + obj.map_decimal_col["my_decimal2"] = realm::decimal128(2.234); + + obj.map_binary_col["my_binary"] = std::vector({0,0,0,0,1,1,1,1}); + + obj.map_date_col["my_date"] = std::chrono::system_clock::from_time_t(date); + obj.map_date_col["my_date2"] = std::chrono::system_clock::from_time_t(date + 10); + + obj.map_enum_col["my_enum"] = AllTypesObject::Enum::two; + + obj.map_mixed_col["my_mixed"] = realm::mixed(std::string("foo_value")); + obj.map_mixed_col["my_mixed_numeric"] = realm::mixed((int64_t)1); + obj.map_mixed_col["my_mixed_numeric2"] = realm::mixed((int64_t)2); + + obj.map_link_col["my_link"] = &obj_link; + obj.map_embedded_col["my_embedded_link"] = &embedded_obj; + + return realm.write([&]() { + return realm.add(std::move(obj)); + }); + }; + + auto managed_obj = create_obj(0); + auto managed_obj2 = create_obj(1); + auto managed_obj3 = create_obj(2); + + auto do_query = [&](std::function&)>&& fn) -> size_t { + auto res = realm.objects().where([fn = std::move(fn)](realm::managed& o) { + return fn(o); + }); + return res.size(); + }; + + // Int + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_int_col["one"] == 1; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_int_col["one"] == 2; }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_int_col["one"] != 2; }) == 3); + + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_int_col["two"] > 1; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_int_col["two"] >= 1; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_int_col["two"] < 2; }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_int_col["two"] <= 2; }) == 3); + + // Bool + + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_bool_col["is_true"] == true; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_bool_col["is_true"] == false; }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_bool_col["is_true"] != false; }) == 3); + + // Double + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_double_col["my_double"] == 1.234; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_double_col["my_double"] == 2.234; }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_double_col["my_double"] != 2.234; }) == 3); + + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_double_col["my_double"] > 1; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_double_col["my_double"] >= 1; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_double_col["my_double2"] < 2; }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_double_col["my_double2"] <= 2.234; }) == 3); + + // String + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_str_col["name"] == "Fido"; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_str_col["name"] == "Bill"; }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_str_col["name"] != "NA"; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_str_col["name"].contains("ido"); }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_str_col["name"].contains("NA"); }) == 0); + + // UUID + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_uuid_col["my_uuid"] == realm::uuid("18de7916-7f84-11ec-a8a3-0242ac120002"); }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_uuid_col["my_uuid"] == realm::uuid("20de7916-7f84-11ec-a8a3-0242ac120002"); }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_uuid_col["my_uuid"] != realm::uuid("20de7916-7f84-11ec-a8a3-0242ac120002"); }) == 3); + + // Object ID + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_object_id_col["my_object_id"] == obj_id; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_object_id_col["my_object_id"] == realm::object_id::generate(); }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_object_id_col["my_object_id"] != realm::object_id::generate(); }) == 3); + + // Decimal + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_decimal_col["my_decimal"] == realm::decimal128(1.234); }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_decimal_col["my_decimal"] == realm::decimal128(2.234); }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_decimal_col["my_decimal"] != realm::decimal128(2.234); }) == 3); + + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_decimal_col["my_decimal"] > 1; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_decimal_col["my_decimal"] >= 1; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_decimal_col["my_decimal2"] < 2; }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_decimal_col["my_decimal2"] <= 2.234; }) == 3); + + // Binary + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_binary_col["my_binary"] == std::vector({0,0,0,0,1,1,1,1}); }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_binary_col["my_binary"] == std::vector({1,0,0,0,1,1,1,1}); }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_binary_col["my_binary"] != std::vector({1,0,0,0,1,1,1,1}); }) == 3); + + // Date + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_date_col["my_date"] == std::chrono::system_clock::from_time_t(date); }) == 3); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_date_col["my_date"] == std::chrono::system_clock::from_time_t(date + 10); }) == 0); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_date_col["my_date"] != std::chrono::system_clock::from_time_t(date + 10); }) == 3); + + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_date_col["my_date"] > std::chrono::system_clock::from_time_t(date - 1); }) == 3); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_date_col["my_date"] >= std::chrono::system_clock::from_time_t(date); }) == 3); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_date_col["my_date2"] < std::chrono::system_clock::from_time_t(date + 10); }) == 0); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_date_col["my_date2"] <= std::chrono::system_clock::from_time_t(date + 20); }) == 3); + + // Enum + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_enum_col["my_enum"] == AllTypesObject::Enum::two; }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_enum_col["my_enum"] == AllTypesObject::Enum::one; }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_enum_col["my_enum"] != AllTypesObject::Enum::one; }) == 3); + + // Mixed + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_mixed_col["my_mixed"] == realm::mixed(std::string("foo_value")); }) == 3); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_mixed_col["my_mixed"] == realm::mixed(std::string("bar_value")); }) == 0); + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_mixed_col["my_mixed"] != realm::mixed(std::string("bar_value")); }) == 3); + + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_mixed_col["my_mixed_numeric"] > (int64_t)1; }) == 0); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_mixed_col["my_mixed_numeric"] >= (int64_t)1; }) == 3); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_mixed_col["my_mixed_numeric2"] < (int64_t)2; }) == 0); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_mixed_col["my_mixed_numeric2"] <= (int64_t)2; }) == 3); + + // Link + auto links = realm.objects().where([](auto& o) { return o._id == 0; }); + CHECK(links.size() == 1); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_link_col["my_link"] == links[0]; }) == 1); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_link_col["my_link"] != links[0]; }) == 2); + + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_link_col["my_link"]->str_col == "foo"; }) == 3); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_link_col["my_link"]->str_col == "bar"; }) == 0); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_link_col["my_link"]->str_col != "bar"; }) == 3); + + // RHS is null + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_link_col["my_link"] == managed_obj.opt_obj_col; }) == 0); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_link_col["my_link"] != managed_obj.opt_obj_col; }) == 3); + + auto managed_link = realm.write([&]() { + AllTypesObjectLink link; + link._id = 1234; + return realm.add(std::move(link)); + }); + realm.write([&]() { + managed_obj.map_link_col["my_link"] = managed_link; + }); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_link_col["my_link"] == managed_link; }) == 1); + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_link_col["my_link"] == managed_link; }) != 2); + + // Embedded + CHECK(do_query([&](realm::managed& o) -> rbool { return o.map_embedded_col["my_embedded_link"]->str_col == "foo_embedded"; }) == 3); + + // Test non existent key + CHECK(do_query([](realm::managed& o) -> rbool { return o.map_int_col["NA"] == 1; }) == 0); + } } }