From e62f7c94aa01f855a40a2b89fc2d9d0f63369d13 Mon Sep 17 00:00:00 2001 From: James Stone Date: Thu, 4 Aug 2022 01:25:51 -0700 Subject: [PATCH] Query support for constant lists and list vs list (#5663) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jørgen Edelbo --- CHANGELOG.md | 2 + src/realm.h | 13 +- src/realm/object-store/c_api/query.cpp | 83 ++-- src/realm/parser/driver.cpp | 199 +++++++-- src/realm/parser/driver.hpp | 46 +- src/realm/parser/generated/query_bison.cpp | 469 +++++++++++---------- src/realm/parser/generated/query_bison.hpp | 4 +- src/realm/parser/generated/query_flex.cpp | 31 +- src/realm/parser/generated/query_flex.hpp | 7 +- src/realm/parser/query_bison.yy | 9 +- src/realm/parser/query_parser.hpp | 30 +- src/realm/query_expression.cpp | 2 +- src/realm/query_expression.hpp | 384 ++++++++++++----- src/realm/table.hpp | 13 +- src/realm/util/serializer.cpp | 19 +- src/realm/util/serializer.hpp | 2 +- test/object-store/c_api/c_api.cpp | 95 +++-- test/test_parser.cpp | 348 +++++++++++++-- test/test_query.cpp | 2 +- 19 files changed, 1219 insertions(+), 539 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ed3f9e4840..c7e92543cc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ # NEXT RELEASE ### Enhancements +* (PR [#????](https://github.com/realm/realm-core/pull/????)) * Allow multiple anonymous sessions. ([PR #5693](https://github.com/realm/realm-core/pull/5693)). +* Introducing query parser support for constant list expressions such as `fruit IN {'apple', 'orange'}`. This also includes general query support for list vs list matching such as `NONE fruits IN {'apple', 'orange'}`. ([Issue #4266](https://github.com/realm/realm-core/issues/4266)) ### Fixed * Fix error message when validating outgoing links from asymmetric objects to non-embedded objects. ([PR #5702](https://github.com/realm/realm-core/pull/5702)) diff --git a/src/realm.h b/src/realm.h index bae2f0becff..b8a69095313 100644 --- a/src/realm.h +++ b/src/realm.h @@ -202,6 +202,11 @@ typedef struct realm_key_path_array { size_t nb_elements; realm_key_path_t* paths; } realm_key_path_array_t; +typedef struct realm_query_arg { + size_t nb_args; + bool is_list; + realm_value_t* arg; +} realm_query_arg_t; typedef struct realm_version_id { uint64_t version; @@ -2186,7 +2191,7 @@ RLM_API realm_dictionary_t* realm_dictionary_from_thread_safe_reference(const re * exception occurred. */ RLM_API realm_query_t* realm_query_parse(const realm_t*, realm_class_key_t target_table, const char* query_string, - size_t num_args, const realm_value_t* args); + size_t num_args, const realm_query_arg_t* args); /** @@ -2213,7 +2218,7 @@ RLM_API const char* realm_query_get_description(realm_query_t*); * exception occurred. */ RLM_API realm_query_t* realm_query_append_query(const realm_query_t*, const char* query_string, size_t num_args, - const realm_value_t* args); + const realm_query_arg_t* args); /** * Parse a query string and bind it to a list. @@ -2230,7 +2235,7 @@ RLM_API realm_query_t* realm_query_append_query(const realm_query_t*, const char * exception occurred. */ RLM_API realm_query_t* realm_query_parse_for_list(const realm_list_t* target_list, const char* query_string, - size_t num_args, const realm_value_t* args); + size_t num_args, const realm_query_arg_t* args); /** * Parse a query string and bind it to another query result. @@ -2248,7 +2253,7 @@ RLM_API realm_query_t* realm_query_parse_for_list(const realm_list_t* target_lis * exception occurred. */ RLM_API realm_query_t* realm_query_parse_for_results(const realm_results_t* target_results, const char* query_string, - size_t num_args, const realm_value_t* args); + size_t num_args, const realm_query_arg_t* args); /** * Count the number of objects found by this query. diff --git a/src/realm/object-store/c_api/query.cpp b/src/realm/object-store/c_api/query.cpp index acd27bbf70d..7e35e70027c 100644 --- a/src/realm/object-store/c_api/query.cpp +++ b/src/realm/object-store/c_api/query.cpp @@ -11,9 +11,9 @@ namespace realm::c_api { namespace { struct QueryArgumentsAdapter : query_parser::Arguments { - const realm_value_t* m_args = nullptr; + const realm_query_arg_t* m_args = nullptr; - QueryArgumentsAdapter(size_t num_args, const realm_value_t* args) noexcept + QueryArgumentsAdapter(size_t num_args, const realm_query_arg_t* args) noexcept : Arguments(num_args) , m_args(args) { @@ -22,8 +22,8 @@ struct QueryArgumentsAdapter : query_parser::Arguments { bool bool_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_BOOL) { - return m_args[i].boolean; + if (m_args[i].arg[0].type == RLM_TYPE_BOOL) { + return m_args[i].arg[0].boolean; } // Note: Unreachable. throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE @@ -32,8 +32,8 @@ struct QueryArgumentsAdapter : query_parser::Arguments { long long long_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_INT) { - return m_args[i].integer; + if (m_args[i].arg[0].type == RLM_TYPE_INT) { + return m_args[i].arg[0].integer; } // Note: Unreachable. throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE @@ -42,8 +42,8 @@ struct QueryArgumentsAdapter : query_parser::Arguments { float float_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_FLOAT) { - return m_args[i].fnum; + if (m_args[i].arg[0].type == RLM_TYPE_FLOAT) { + return m_args[i].arg[0].fnum; } // Note: Unreachable. throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE @@ -52,8 +52,8 @@ struct QueryArgumentsAdapter : query_parser::Arguments { double double_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_DOUBLE) { - return m_args[i].dnum; + if (m_args[i].arg[0].type == RLM_TYPE_DOUBLE) { + return m_args[i].arg[0].dnum; } // Note: Unreachable. throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE @@ -62,8 +62,8 @@ struct QueryArgumentsAdapter : query_parser::Arguments { StringData string_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_STRING) { - return from_capi(m_args[i].string); + if (m_args[i].arg[0].type == RLM_TYPE_STRING) { + return from_capi(m_args[i].arg[0].string); } // Note: Unreachable. throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE @@ -72,8 +72,8 @@ struct QueryArgumentsAdapter : query_parser::Arguments { BinaryData binary_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_BINARY) { - return from_capi(m_args[i].binary); + if (m_args[i].arg[0].type == RLM_TYPE_BINARY) { + return from_capi(m_args[i].arg[0].binary); } // Note: Unreachable. throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE @@ -82,8 +82,8 @@ struct QueryArgumentsAdapter : query_parser::Arguments { Timestamp timestamp_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_TIMESTAMP) { - return from_capi(m_args[i].timestamp); + if (m_args[i].arg[0].type == RLM_TYPE_TIMESTAMP) { + return from_capi(m_args[i].arg[0].timestamp); } // Note: Unreachable. throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE @@ -92,9 +92,9 @@ struct QueryArgumentsAdapter : query_parser::Arguments { ObjKey object_index_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_LINK) { + if (m_args[i].arg[0].type == RLM_TYPE_LINK) { // FIXME: Somehow check the target table type? - return from_capi(m_args[i].link).get_obj_key(); + return from_capi(m_args[i].arg[0].link).get_obj_key(); } // Note: Unreachable. throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE @@ -103,8 +103,8 @@ struct QueryArgumentsAdapter : query_parser::Arguments { ObjectId objectid_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_OBJECT_ID) { - return from_capi(m_args[i].object_id); + if (m_args[i].arg[0].type == RLM_TYPE_OBJECT_ID) { + return from_capi(m_args[i].arg[0].object_id); } // Note: Unreachable. throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE @@ -113,8 +113,8 @@ struct QueryArgumentsAdapter : query_parser::Arguments { Decimal128 decimal128_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_DECIMAL128) { - return from_capi(m_args[i].decimal128); + if (m_args[i].arg[0].type == RLM_TYPE_DECIMAL128) { + return from_capi(m_args[i].arg[0].decimal128); } // Note: Unreachable. throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE @@ -123,8 +123,8 @@ struct QueryArgumentsAdapter : query_parser::Arguments { UUID uuid_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_UUID) { - return from_capi(m_args[i].uuid); + if (m_args[i].arg[0].type == RLM_TYPE_UUID) { + return from_capi(m_args[i].arg[0].uuid); } // Note: Unreachable. throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE @@ -133,8 +133,8 @@ struct QueryArgumentsAdapter : query_parser::Arguments { ObjLink objlink_for_argument(size_t i) final { verify_ndx(i); - if (m_args[i].type == RLM_TYPE_LINK) { - return from_capi(m_args[i].link); + if (m_args[i].arg[0].type == RLM_TYPE_LINK) { + return from_capi(m_args[i].arg[0].link); } throw LogicError{LogicError::type_mismatch}; // LCOV_EXCL_LINE } @@ -142,13 +142,30 @@ struct QueryArgumentsAdapter : query_parser::Arguments { bool is_argument_null(size_t i) final { verify_ndx(i); - return m_args[i].type == RLM_TYPE_NULL; + return !m_args[i].is_list && m_args[i].arg[0].type == RLM_TYPE_NULL; } + bool is_argument_list(size_t i) final + { + verify_ndx(i); + return m_args[i].is_list; + } + + std::vector list_for_argument(size_t ndx) final + { + verify_ndx(ndx); + REALM_ASSERT(m_args[ndx].is_list); + std::vector list; + list.reserve(m_args[ndx].nb_args); + for (size_t i = 0; i < m_args[ndx].nb_args; ++i) { + list.push_back(from_capi(m_args[ndx].arg[i])); + } + return list; + } DataType type_for_argument(size_t i) override { verify_ndx(i); - switch (m_args[i].type) { + switch (m_args[i].arg[0].type) { case RLM_TYPE_NULL: // LCOV_EXCL_LINE REALM_TERMINATE("Query parser did not call is_argument_null()"); // LCOV_EXCL_LINE case RLM_TYPE_INT: @@ -180,7 +197,7 @@ struct QueryArgumentsAdapter : query_parser::Arguments { } // namespace static Query parse_and_apply_query(const std::shared_ptr& realm, ConstTableRef table, const char* query_string, - size_t num_args, const realm_value_t* args) + size_t num_args, const realm_query_arg_t* args) { query_parser::KeyPathMapping mapping; realm::populate_keypath_mapping(mapping, *realm); @@ -190,7 +207,7 @@ static Query parse_and_apply_query(const std::shared_ptr& realm, ConstTab } RLM_API realm_query_t* realm_query_parse(const realm_t* realm, realm_class_key_t target_table_key, - const char* query_string, size_t num_args, const realm_value_t* args) + const char* query_string, size_t num_args, const realm_query_arg_t* args) { return wrap_err([&]() { auto table = (*realm)->read_group().get_table(TableKey(target_table_key)); @@ -208,7 +225,7 @@ RLM_API const char* realm_query_get_description(realm_query_t* query) } RLM_API realm_query_t* realm_query_append_query(const realm_query_t* existing_query, const char* query_string, - size_t num_args, const realm_value_t* args) + size_t num_args, const realm_query_arg_t* args) { return wrap_err([&]() { auto realm = existing_query->weak_realm.lock(); @@ -225,7 +242,7 @@ RLM_API realm_query_t* realm_query_append_query(const realm_query_t* existing_qu } RLM_API realm_query_t* realm_query_parse_for_list(const realm_list_t* list, const char* query_string, size_t num_args, - const realm_value_t* args) + const realm_query_arg_t* args) { return wrap_err([&]() { auto realm = list->get_realm(); @@ -237,7 +254,7 @@ RLM_API realm_query_t* realm_query_parse_for_list(const realm_list_t* list, cons } RLM_API realm_query_t* realm_query_parse_for_results(const realm_results_t* results, const char* query_string, - size_t num_args, const realm_value_t* args) + size_t num_args, const realm_query_arg_t* args) { return wrap_err([&]() { auto realm = results->get_realm(); diff --git a/src/realm/parser/driver.cpp b/src/realm/parser/driver.cpp index fb1b2cf0984..ae03b21a3c1 100644 --- a/src/realm/parser/driver.cpp +++ b/src/realm/parser/driver.cpp @@ -35,19 +35,22 @@ const char* agg_op_type_to_str(query_parser::AggrNode::Type type) return ""; } -const char* expression_cmp_type_to_str(ExpressionComparisonType type) +const char* expression_cmp_type_to_str(util::Optional type) { - switch (type) { - case ExpressionComparisonType::Any: - return "ANY"; - case ExpressionComparisonType::All: - return "ALL"; - case ExpressionComparisonType::None: - return "NONE"; + if (type) { + switch (*type) { + case ExpressionComparisonType::Any: + return "ANY"; + case ExpressionComparisonType::All: + return "ALL"; + case ExpressionComparisonType::None: + return "NONE"; + } } return ""; } + static std::map opstr = { {CompareNode::EQUAL, "="}, {CompareNode::NOT_EQUAL, "!="}, @@ -155,6 +158,17 @@ inline T string_to(const std::string& s) class MixedArguments : public query_parser::Arguments { public: MixedArguments(const std::vector& args) + : Arguments(args.size()) + , m_args([](const std::vector& list) -> std::vector> { + std::vector> ret; + for (const Mixed& m : list) { + ret.push_back({m}); + } + return ret; + }(args)) + { + } + MixedArguments(const std::vector>& args) : Arguments(args.size()) , m_args(args) { @@ -162,76 +176,86 @@ class MixedArguments : public query_parser::Arguments { bool bool_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); } long long long_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); } float float_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); } double double_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); } StringData string_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); } BinaryData binary_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); } Timestamp timestamp_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); } ObjectId objectid_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); } UUID uuid_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); } Decimal128 decimal128_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); } ObjKey object_index_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); } ObjLink objlink_for_argument(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).get(); + return m_args.at(n)[0].get(); + } + std::vector list_for_argument(size_t n) final + { + Arguments::verify_ndx(n); + return m_args.at(n); } bool is_argument_null(size_t n) final { Arguments::verify_ndx(n); - return m_args.at(n).is_null(); + return m_args.at(n).size() == 0 || m_args.at(n)[0].is_null(); + } + bool is_argument_list(size_t n) final + { + Arguments::verify_ndx(n); + return m_args.at(n).size() > 1; } DataType type_for_argument(size_t n) { Arguments::verify_ndx(n); - return m_args.at(n).get_type(); + return m_args.at(n)[0].get_type(); } private: - const std::vector& m_args; + const std::vector> m_args; }; Timestamp get_timestamp_if_valid(int64_t seconds, int32_t nanoseconds) @@ -304,10 +328,11 @@ std::unique_ptr OperationNode::visit(ParserDriver* drv, DataType type) std::unique_ptr left; std::unique_ptr right; - auto left_is_constant = m_left->is_constant(); - auto right_is_constant = m_right->is_constant(); + const bool left_is_constant = m_left->is_constant(); + const bool right_is_constant = m_right->is_constant(); + const bool produces_multiple_values = m_left->is_list() || m_right->is_list(); - if (left_is_constant && right_is_constant) { + if (left_is_constant && right_is_constant && !produces_multiple_values) { right = m_right->visit(drv, type); left = m_left->visit(drv, type); auto v_left = left->get_mixed(); @@ -417,7 +442,8 @@ Query EqualityNode::visit(ParserDriver* drv) if (op == CompareNode::IN) { Subexpr* r = right.get(); if (!r->has_multiple_values()) { - throw InvalidQueryArgError("The keypath following 'IN' must contain a list"); + throw InvalidQueryArgError("The keypath following 'IN' must contain a list. Found '" + + r->description(drv->m_serializer_state) + "'"); } } @@ -464,8 +490,8 @@ Query EqualityNode::visit(ParserDriver* drv) } else if (left_type == type_Link) { auto link_column = dynamic_cast*>(left.get()); - if (link_column && link_column->link_map().get_nb_hops() == 1 && - link_column->get_comparison_type() == ExpressionComparisonType::Any) { + if (link_column && link_column->link_map().get_nb_hops() == 1 && link_column->get_comparison_type() && + *link_column->get_comparison_type() == ExpressionComparisonType::Any) { // We can use equal/not_equal and get a LinksToNode based query if (op == CompareNode::EQUAL) { return drv->m_base_table->where().equal(link_column->link_map().get_first_column_key(), val); @@ -507,7 +533,8 @@ Query BetweenNode::visit(ParserDriver* drv) if (dynamic_cast(prop->visit(drv, type_Int).get())) { // It's a list! - if (dynamic_cast(prop->prop)->comp_type != ExpressionComparisonType::All) { + util::Optional cmp_type = dynamic_cast(prop->prop)->comp_type; + if (!cmp_type || *cmp_type != ExpressionComparisonType::All) { throw InvalidQueryError("Only 'ALL' supported for operator 'BETWEEN' when applied to lists."); } } @@ -1007,9 +1034,7 @@ std::unique_ptr ConstantNode::visit(ParserDriver* drv, DataType hint) case Type::BASE64: { const size_t encoded_size = text.size() - 5; size_t buffer_size = util::base64_decoded_size(encoded_size); - drv->m_args.buffer_space.push_back({}); - auto& decode_buffer = drv->m_args.buffer_space.back(); - decode_buffer.resize(buffer_size); + std::string decode_buffer(buffer_size, char(0)); StringData window(text.c_str() + 4, encoded_size); util::Optional decoded_size = util::base64_decode(window, decode_buffer.data(), buffer_size); if (!decoded_size) { @@ -1017,15 +1042,16 @@ std::unique_ptr ConstantNode::visit(ParserDriver* drv, DataType hint) } REALM_ASSERT_DEBUG_EX(*decoded_size <= encoded_size, *decoded_size, encoded_size); decode_buffer.resize(*decoded_size); // truncate - + drv->m_args.buffer_space.push_back(OwnedData{decode_buffer.data(), decode_buffer.size()}); + const char* data = drv->m_args.buffer_space.back().data(); if (hint == type_String) { - ret = std::make_unique(StringData(decode_buffer.data(), decode_buffer.size())); + ret = std::make_unique(StringData(data, decode_buffer.size())); } if (hint == type_Binary) { - ret = std::make_unique>(BinaryData(decode_buffer.data(), decode_buffer.size())); + ret = std::make_unique>(BinaryData(data, decode_buffer.size())); } if (hint == type_Mixed) { - ret = std::make_unique>(BinaryData(decode_buffer.data(), decode_buffer.size())); + ret = std::make_unique>(BinaryData(data, decode_buffer.size())); } break; } @@ -1107,10 +1133,29 @@ std::unique_ptr ConstantNode::visit(ParserDriver* drv, DataType hint) break; case Type::ARG: { size_t arg_no = size_t(strtol(text.substr(1).c_str(), nullptr, 10)); + if (m_comp_type && !drv->m_args.is_argument_list(arg_no)) { + throw InvalidQueryError(util::format( + "ANY/ALL/NONE are only allowed on arguments which contain a list but '%1' is not a list.", + explain_value_message)); + } if (drv->m_args.is_argument_null(arg_no)) { explain_value_message = util::format("argument '%1' which is NULL", explain_value_message); ret = std::make_unique>(realm::null()); } + else if (drv->m_args.is_argument_list(arg_no)) { + std::vector mixed_list = drv->m_args.list_for_argument(arg_no); + std::unique_ptr> values = std::make_unique>(); + constexpr bool is_list = true; + values->init(is_list, mixed_list.size()); + size_t ndx = 0; + for (auto& val : mixed_list) { + values->set(ndx++, val); + } + if (m_comp_type) { + values->set_comparison_type(*m_comp_type); + } + ret = std::move(values); + } else { auto type = drv->m_args.type_for_argument(arg_no); explain_value_message = @@ -1208,7 +1253,64 @@ std::unique_ptr ConstantNode::visit(ParserDriver* drv, DataType hint) return ret; } -LinkChain PathNode::visit(ParserDriver* drv, ExpressionComparisonType comp_type) +std::unique_ptr ListNode::visit(ParserDriver* drv, DataType hint) +{ + if (hint == type_TypeOfValue) { + try { + std::unique_ptr> ret = std::make_unique>(); + constexpr bool is_list = true; + ret->init(is_list, elements.size()); + ret->set_comparison_type(m_comp_type); + size_t ndx = 0; + for (auto constant : elements) { + std::unique_ptr evaluated = constant->visit(drv, hint); + if (auto converted = dynamic_cast*>(evaluated.get())) { + ret->set(ndx++, converted->get(0)); + } + else { + throw InvalidQueryError(util::format("Invalid constant inside constant list: %1", + evaluated->description(drv->m_serializer_state))); + } + } + return ret; + } + catch (const std::runtime_error& e) { + throw InvalidQueryArgError(e.what()); + } + } + + std::unique_ptr> ret = std::make_unique>(); + constexpr bool is_list = true; + ret->init(is_list, elements.size()); + ret->set_comparison_type(m_comp_type); + size_t ndx = 0; + for (auto constant : elements) { + auto evaulated_constant = constant->visit(drv, hint); + if (auto value = dynamic_cast(evaulated_constant.get())) { + REALM_ASSERT_EX(value->size() == 1, value->size()); + Mixed mixed = value->get(0); + if (mixed.is_type(type_String)) { + StringData str = mixed.get_string(); + drv->m_args.buffer_space.push_back(OwnedData{str.data(), str.size()}); + ret->set(ndx++, StringData(drv->m_args.buffer_space.back().data(), str.size())); + } + else if (mixed.is_type(type_Binary)) { + BinaryData bin = mixed.get_binary(); + drv->m_args.buffer_space.push_back(OwnedData{bin.data(), bin.size()}); + ret->set(ndx++, BinaryData(drv->m_args.buffer_space.back().data(), bin.size())); + } + else { + ret->set(ndx++, value->get(0)); + } + } + else { + throw InvalidQueryError("Invalid constant inside constant list"); + } + } + return ret; +} + +LinkChain PathNode::visit(ParserDriver* drv, util::Optional comp_type) { LinkChain link_chain(drv->m_base_table, comp_type); for (std::string path_elem : path_elems) { @@ -1229,7 +1331,7 @@ LinkChain PathNode::visit(ParserDriver* drv, ExpressionComparisonType comp_type) try { link_chain.link(path_elem); } - // I case of exception, we have to throw InvalidQueryError + // In case of exception, we have to throw InvalidQueryError catch (const std::runtime_error& e) { auto str = e.what(); StringData table_name = drv->get_printable_name(link_chain.get_current_table()->get_name()); @@ -1305,10 +1407,6 @@ static void verify_conditions(Subexpr* left, Subexpr* right, util::serializer::S util::format("Ordered comparison between two primitive lists is not implemented yet ('%1' and '%2')", left->description(state), right->description(state))); } - if (left->has_multiple_values() && right->has_multiple_values()) { - throw InvalidQueryError(util::format("Comparison between two lists is not supported ('%1' and '%2')", - left->description(state), right->description(state))); - } if (dynamic_cast*>(left) && dynamic_cast*>(right)) { throw InvalidQueryError(util::format("Comparison between two constants is not supported ('%1' and '%2')", left->description(state), right->description(state))); @@ -1464,6 +1562,12 @@ std::string check_escapes(const char* str) } // namespace query_parser +Query Table::query(const std::string& query_string, const std::vector>& arguments) const +{ + MixedArguments args(arguments); + return query(query_string, args, {}); +} + Query Table::query(const std::string& query_string, const std::vector& arguments) const { MixedArguments args(arguments); @@ -1477,6 +1581,13 @@ Query Table::query(const std::string& query_string, const std::vector& ar return query(query_string, args, mapping); } +Query Table::query(const std::string& query_string, const std::vector>& arguments, + const query_parser::KeyPathMapping& mapping) const +{ + MixedArguments args(arguments); + return query(query_string, args, mapping); +} + Query Table::query(const std::string& query_string, query_parser::Arguments& args, const query_parser::KeyPathMapping& mapping) const { @@ -1565,7 +1676,7 @@ std::unique_ptr LinkChain::column(const std::string& col) } } else { - if (m_comparison_type != ExpressionComparisonType::Any && list_count == 0) { + if (m_comparison_type && list_count == 0) { throw InvalidQueryError(util::format("The keypath following '%1' must contain a list", expression_cmp_type_to_str(m_comparison_type))); } diff --git a/src/realm/parser/driver.hpp b/src/realm/parser/driver.hpp index 8c7be09a0f2..5f57c693b63 100644 --- a/src/realm/parser/driver.hpp +++ b/src/realm/parser/driver.hpp @@ -140,22 +140,37 @@ class ConstantNode : public ParserNode { , text(str) { } + ConstantNode(ExpressionComparisonType comp_type, const std::string& str) + : type(Type::ARG) + , text(str) + , m_comp_type(comp_type) + { + } std::unique_ptr visit(ParserDriver*, DataType); + util::Optional m_comp_type; }; class ListNode : public ParserNode { public: std::vector elements; + ListNode() = default; ListNode(ConstantNode* elem) { elements.emplace_back(elem); } - void add_element(ConstantNode* elem) { elements.emplace_back(elem); } + void set_comp_type(ExpressionComparisonType comp_type) + { + m_comp_type = comp_type; + } + std::unique_ptr visit(ParserDriver*, DataType); + +private: + util::Optional m_comp_type; }; class PropertyNode : public ParserNode { @@ -169,6 +184,10 @@ class ExpressionNode : public ParserNode { { return false; } + virtual bool is_list() + { + return false; + } virtual std::unique_ptr visit(ParserDriver*, DataType = type_Int) = 0; }; @@ -176,6 +195,7 @@ class ValueNode : public ExpressionNode { public: ConstantNode* constant = nullptr; PropertyNode* prop = nullptr; + ListNode* list = nullptr; ValueNode(ConstantNode* node) : constant(node) @@ -185,20 +205,25 @@ class ValueNode : public ExpressionNode { : prop(node) { } + ValueNode(ListNode* node) + : list(node) + { + } bool is_constant() final { - return constant != nullptr; + return constant != nullptr || list != nullptr; } - ConstantNode* get_constant() + bool is_list() final { - REALM_ASSERT(is_constant()); - return constant; + return list != nullptr; } std::unique_ptr visit(ParserDriver* drv, DataType type) override { if (prop) return prop->visit(drv); + if (list) + return list->visit(drv, type); return constant->visit(drv, type); } }; @@ -218,6 +243,10 @@ class OperationNode : public ExpressionNode { { return m_left->is_constant() && m_right->is_constant(); } + bool is_list() final + { + return m_left->is_list() || m_right->is_list(); + } std::unique_ptr visit(ParserDriver*, DataType) override; }; @@ -319,7 +348,7 @@ class PathNode : public ParserNode { public: std::vector path_elems; - LinkChain visit(ParserDriver*, ExpressionComparisonType = ExpressionComparisonType::Any); + LinkChain visit(ParserDriver*, util::Optional = util::none); void add_element(const std::string& str) { path_elems.push_back(str); @@ -362,7 +391,7 @@ class PropNode : public PropertyNode { public: PathNode* path; std::string identifier; - ExpressionComparisonType comp_type = ExpressionComparisonType::Any; + util::Optional comp_type = util::none; PostOpNode* post_op = nullptr; ConstantNode* index = nullptr; @@ -374,7 +403,7 @@ class PropNode : public PropertyNode { { } PropNode(PathNode* node, std::string id, PostOpNode* po_node, - ExpressionComparisonType ct = ExpressionComparisonType::Any) + util::Optional ct = util::none) : path(node) , identifier(id) , comp_type(ct) @@ -384,7 +413,6 @@ class PropNode : public PropertyNode { PropNode(PathNode* node, std::string id) : path(node) , identifier(id) - , comp_type(ExpressionComparisonType::Any) { } std::unique_ptr visit(ParserDriver*) override; diff --git a/src/realm/parser/generated/query_bison.cpp b/src/realm/parser/generated/query_bison.cpp index 1237035ee0c..fffce220d00 100644 --- a/src/realm/parser/generated/query_bison.cpp +++ b/src/realm/parser/generated/query_bison.cpp @@ -1493,319 +1493,335 @@ namespace yy { { yylhs.value.as < ValueNode* > () = drv.m_parse_nodes.create(yystack_[0].value.as < PropertyNode* > ());} break; - case 23: // prop: path id post_op + case 23: // value: list + { yylhs.value.as < ValueNode* > () = drv.m_parse_nodes.create(yystack_[0].value.as < ListNode* > ());} + break; + + case 24: // prop: path id post_op { yylhs.value.as < PropertyNode* > () = drv.m_parse_nodes.create(yystack_[2].value.as < PathNode* > (), yystack_[1].value.as < std::string > (), yystack_[0].value.as < PostOpNode* > ()); } break; - case 24: // prop: path id '[' constant ']' post_op + case 25: // prop: path id '[' constant ']' post_op { yylhs.value.as < PropertyNode* > () = drv.m_parse_nodes.create(yystack_[5].value.as < PathNode* > (), yystack_[4].value.as < std::string > (), yystack_[2].value.as < ConstantNode* > (), yystack_[0].value.as < PostOpNode* > ()); } break; - case 25: // prop: comp_type path id post_op + case 26: // prop: comp_type path id post_op { yylhs.value.as < PropertyNode* > () = drv.m_parse_nodes.create(yystack_[2].value.as < PathNode* > (), yystack_[1].value.as < std::string > (), yystack_[0].value.as < PostOpNode* > (), ExpressionComparisonType(yystack_[3].value.as < int > ())); } break; - case 26: // prop: path "@links" post_op + case 27: // prop: path "@links" post_op { yylhs.value.as < PropertyNode* > () = drv.m_parse_nodes.create(yystack_[2].value.as < PathNode* > (), "@links", yystack_[0].value.as < PostOpNode* > ()); } break; - case 27: // prop: path id '.' aggr_op '.' id + case 28: // prop: path id '.' aggr_op '.' id { yylhs.value.as < PropertyNode* > () = drv.m_parse_nodes.create(yystack_[5].value.as < PathNode* > (), yystack_[4].value.as < std::string > (), yystack_[2].value.as < AggrNode* > (), yystack_[0].value.as < std::string > ()); } break; - case 28: // prop: path id '.' aggr_op + case 29: // prop: path id '.' aggr_op { yylhs.value.as < PropertyNode* > () = drv.m_parse_nodes.create(yystack_[3].value.as < PathNode* > (), yystack_[2].value.as < std::string > (), yystack_[0].value.as < AggrNode* > ()); } break; - case 29: // prop: subquery + case 30: // prop: subquery { yylhs.value.as < PropertyNode* > () = yystack_[0].value.as < SubqueryNode* > (); } break; - case 30: // simple_prop: path id + case 31: // simple_prop: path id { yylhs.value.as < PropNode* > () = drv.m_parse_nodes.create(yystack_[1].value.as < PathNode* > (), yystack_[0].value.as < std::string > ()); } break; - case 31: // subquery: "subquery" '(' simple_prop ',' id ',' query ')' '.' "@size" + case 32: // subquery: "subquery" '(' simple_prop ',' id ',' query ')' '.' "@size" { yylhs.value.as < SubqueryNode* > () = drv.m_parse_nodes.create(yystack_[7].value.as < PropNode* > (), yystack_[5].value.as < std::string > (), yystack_[3].value.as < QueryNode* > ()); } break; - case 32: // post_query: %empty + case 33: // post_query: %empty { yylhs.value.as < DescriptorOrderingNode* > () = drv.m_parse_nodes.create();} break; - case 33: // post_query: post_query sort + case 34: // post_query: post_query sort { yystack_[1].value.as < DescriptorOrderingNode* > ()->add_descriptor(yystack_[0].value.as < DescriptorNode* > ()); yylhs.value.as < DescriptorOrderingNode* > () = yystack_[1].value.as < DescriptorOrderingNode* > (); } break; - case 34: // post_query: post_query distinct + case 35: // post_query: post_query distinct { yystack_[1].value.as < DescriptorOrderingNode* > ()->add_descriptor(yystack_[0].value.as < DescriptorNode* > ()); yylhs.value.as < DescriptorOrderingNode* > () = yystack_[1].value.as < DescriptorOrderingNode* > (); } break; - case 35: // post_query: post_query limit + case 36: // post_query: post_query limit { yystack_[1].value.as < DescriptorOrderingNode* > ()->add_descriptor(yystack_[0].value.as < DescriptorNode* > ()); yylhs.value.as < DescriptorOrderingNode* > () = yystack_[1].value.as < DescriptorOrderingNode* > (); } break; - case 36: // distinct: "distinct" '(' distinct_param ')' + case 37: // distinct: "distinct" '(' distinct_param ')' { yylhs.value.as < DescriptorNode* > () = yystack_[1].value.as < DescriptorNode* > (); } break; - case 37: // distinct_param: path id + case 38: // distinct_param: path id { yylhs.value.as < DescriptorNode* > () = drv.m_parse_nodes.create(DescriptorNode::DISTINCT); yylhs.value.as < DescriptorNode* > ()->add(yystack_[1].value.as < PathNode* > ()->path_elems, yystack_[0].value.as < std::string > ());} break; - case 38: // distinct_param: distinct_param ',' path id + case 39: // distinct_param: distinct_param ',' path id { yystack_[3].value.as < DescriptorNode* > ()->add(yystack_[1].value.as < PathNode* > ()->path_elems, yystack_[0].value.as < std::string > ()); yylhs.value.as < DescriptorNode* > () = yystack_[3].value.as < DescriptorNode* > (); } break; - case 39: // sort: "sort" '(' sort_param ')' + case 40: // sort: "sort" '(' sort_param ')' { yylhs.value.as < DescriptorNode* > () = yystack_[1].value.as < DescriptorNode* > (); } break; - case 40: // sort_param: path id direction + case 41: // sort_param: path id direction { yylhs.value.as < DescriptorNode* > () = drv.m_parse_nodes.create(DescriptorNode::SORT); yylhs.value.as < DescriptorNode* > ()->add(yystack_[2].value.as < PathNode* > ()->path_elems, yystack_[1].value.as < std::string > (), yystack_[0].value.as < bool > ());} break; - case 41: // sort_param: sort_param ',' path id direction + case 42: // sort_param: sort_param ',' path id direction { yystack_[4].value.as < DescriptorNode* > ()->add(yystack_[2].value.as < PathNode* > ()->path_elems, yystack_[1].value.as < std::string > (), yystack_[0].value.as < bool > ()); yylhs.value.as < DescriptorNode* > () = yystack_[4].value.as < DescriptorNode* > (); } break; - case 42: // limit: "limit" '(' "natural0" ')' + case 43: // limit: "limit" '(' "natural0" ')' { yylhs.value.as < DescriptorNode* > () = drv.m_parse_nodes.create(DescriptorNode::LIMIT, yystack_[1].value.as < std::string > ()); } break; - case 43: // direction: "ascending" + case 44: // direction: "ascending" { yylhs.value.as < bool > () = true; } break; - case 44: // direction: "descending" + case 45: // direction: "descending" { yylhs.value.as < bool > () = false; } break; - case 45: // list: '{' list_content '}' - { yylhs.value.as < ListNode* > () = yystack_[1].value.as < ListNode* > (); } + case 46: // list: '{' list_content '}' + { yylhs.value.as < ListNode* > () = yystack_[1].value.as < ListNode* > (); } + break; + + case 47: // list: comp_type '{' list_content '}' + { yystack_[1].value.as < ListNode* > ()->set_comp_type(ExpressionComparisonType(yystack_[3].value.as < int > ())); yylhs.value.as < ListNode* > () = yystack_[1].value.as < ListNode* > (); } break; - case 46: // list_content: constant + case 48: // list_content: constant { yylhs.value.as < ListNode* > () = drv.m_parse_nodes.create(yystack_[0].value.as < ConstantNode* > ()); } break; - case 47: // list_content: list_content ',' constant + case 49: // list_content: %empty + { yylhs.value.as < ListNode* > () = drv.m_parse_nodes.create(); } + break; + + case 50: // list_content: list_content ',' constant { yystack_[2].value.as < ListNode* > ()->add_element(yystack_[0].value.as < ConstantNode* > ()); yylhs.value.as < ListNode* > () = yystack_[2].value.as < ListNode* > (); } break; - case 48: // constant: "natural0" + case 51: // constant: "natural0" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::NUMBER, yystack_[0].value.as < std::string > ()); } break; - case 49: // constant: "number" + case 52: // constant: "number" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::NUMBER, yystack_[0].value.as < std::string > ()); } break; - case 50: // constant: "infinity" + case 53: // constant: "infinity" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::INFINITY_VAL, yystack_[0].value.as < std::string > ()); } break; - case 51: // constant: "NaN" + case 54: // constant: "NaN" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::NAN_VAL, yystack_[0].value.as < std::string > ()); } break; - case 52: // constant: "string" + case 55: // constant: "string" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::STRING, yystack_[0].value.as < std::string > ()); } break; - case 53: // constant: "base64" + case 56: // constant: "base64" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::BASE64, yystack_[0].value.as < std::string > ()); } break; - case 54: // constant: "float" + case 57: // constant: "float" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::FLOAT, yystack_[0].value.as < std::string > ()); } break; - case 55: // constant: "date" + case 58: // constant: "date" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::TIMESTAMP, yystack_[0].value.as < std::string > ()); } break; - case 56: // constant: "UUID" + case 59: // constant: "UUID" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::UUID_T, yystack_[0].value.as < std::string > ()); } break; - case 57: // constant: "ObjectId" + case 60: // constant: "ObjectId" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::OID, yystack_[0].value.as < std::string > ()); } break; - case 58: // constant: "link" + case 61: // constant: "link" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::LINK, yystack_[0].value.as < std::string > ()); } break; - case 59: // constant: "typed link" + case 62: // constant: "typed link" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::TYPED_LINK, yystack_[0].value.as < std::string > ()); } break; - case 60: // constant: "true" + case 63: // constant: "true" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::TRUE, ""); } break; - case 61: // constant: "false" + case 64: // constant: "false" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::FALSE, ""); } break; - case 62: // constant: "null" + case 65: // constant: "null" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::NULL_VAL, ""); } break; - case 63: // constant: "argument" + case 66: // constant: "argument" { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ConstantNode::ARG, yystack_[0].value.as < std::string > ()); } break; - case 64: // boolexpr: "truepredicate" + case 67: // constant: comp_type "argument" + { yylhs.value.as < ConstantNode* > () = drv.m_parse_nodes.create(ExpressionComparisonType(yystack_[1].value.as < int > ()), yystack_[0].value.as < std::string > ()); } + break; + + case 68: // boolexpr: "truepredicate" { yylhs.value.as < TrueOrFalseNode* > () = drv.m_parse_nodes.create(true); } break; - case 65: // boolexpr: "falsepredicate" + case 69: // boolexpr: "falsepredicate" { yylhs.value.as < TrueOrFalseNode* > () = drv.m_parse_nodes.create(false); } break; - case 66: // comp_type: "any" + case 70: // comp_type: "any" { yylhs.value.as < int > () = int(ExpressionComparisonType::Any); } break; - case 67: // comp_type: "all" + case 71: // comp_type: "all" { yylhs.value.as < int > () = int(ExpressionComparisonType::All); } break; - case 68: // comp_type: "none" + case 72: // comp_type: "none" { yylhs.value.as < int > () = int(ExpressionComparisonType::None); } break; - case 69: // post_op: %empty + case 73: // post_op: %empty { yylhs.value.as < PostOpNode* > () = nullptr; } break; - case 70: // post_op: '.' "@size" + case 74: // post_op: '.' "@size" { yylhs.value.as < PostOpNode* > () = drv.m_parse_nodes.create(yystack_[0].value.as < std::string > (), PostOpNode::SIZE);} break; - case 71: // post_op: '.' "@type" + case 75: // post_op: '.' "@type" { yylhs.value.as < PostOpNode* > () = drv.m_parse_nodes.create(yystack_[0].value.as < std::string > (), PostOpNode::TYPE);} break; - case 72: // aggr_op: "@max" + case 76: // aggr_op: "@max" { yylhs.value.as < AggrNode* > () = drv.m_parse_nodes.create(AggrNode::MAX);} break; - case 73: // aggr_op: "@min" + case 77: // aggr_op: "@min" { yylhs.value.as < AggrNode* > () = drv.m_parse_nodes.create(AggrNode::MIN);} break; - case 74: // aggr_op: "@sun" + case 78: // aggr_op: "@sun" { yylhs.value.as < AggrNode* > () = drv.m_parse_nodes.create(AggrNode::SUM);} break; - case 75: // aggr_op: "@average" + case 79: // aggr_op: "@average" { yylhs.value.as < AggrNode* > () = drv.m_parse_nodes.create(AggrNode::AVG);} break; - case 76: // equality: "==" + case 80: // equality: "==" { yylhs.value.as < int > () = CompareNode::EQUAL; } break; - case 77: // equality: "!=" + case 81: // equality: "!=" { yylhs.value.as < int > () = CompareNode::NOT_EQUAL; } break; - case 78: // equality: "in" + case 82: // equality: "in" { yylhs.value.as < int > () = CompareNode::IN; } break; - case 79: // relational: "<" + case 83: // relational: "<" { yylhs.value.as < int > () = CompareNode::LESS; } break; - case 80: // relational: "<=" + case 84: // relational: "<=" { yylhs.value.as < int > () = CompareNode::LESS_EQUAL; } break; - case 81: // relational: ">" + case 85: // relational: ">" { yylhs.value.as < int > () = CompareNode::GREATER; } break; - case 82: // relational: ">=" + case 86: // relational: ">=" { yylhs.value.as < int > () = CompareNode::GREATER_EQUAL; } break; - case 83: // stringop: "beginswith" + case 87: // stringop: "beginswith" { yylhs.value.as < int > () = CompareNode::BEGINSWITH; } break; - case 84: // stringop: "endswith" + case 88: // stringop: "endswith" { yylhs.value.as < int > () = CompareNode::ENDSWITH; } break; - case 85: // stringop: "contains" + case 89: // stringop: "contains" { yylhs.value.as < int > () = CompareNode::CONTAINS; } break; - case 86: // stringop: "like" + case 90: // stringop: "like" { yylhs.value.as < int > () = CompareNode::LIKE; } break; - case 87: // path: %empty + case 91: // path: %empty { yylhs.value.as < PathNode* > () = drv.m_parse_nodes.create(); } break; - case 88: // path: path path_elem + case 92: // path: path path_elem { yystack_[1].value.as < PathNode* > ()->add_element(yystack_[0].value.as < std::string > ()); yylhs.value.as < PathNode* > () = yystack_[1].value.as < PathNode* > (); } break; - case 89: // path_elem: id '.' + case 93: // path_elem: id '.' { yylhs.value.as < std::string > () = yystack_[1].value.as < std::string > (); } break; - case 90: // id: "identifier" + case 94: // id: "identifier" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 91: // id: "@links" '.' "identifier" '.' "identifier" + case 95: // id: "@links" '.' "identifier" '.' "identifier" { yylhs.value.as < std::string > () = std::string("@links.") + yystack_[2].value.as < std::string > () + "." + yystack_[0].value.as < std::string > (); } break; - case 92: // id: "beginswith" + case 96: // id: "beginswith" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 93: // id: "endswith" + case 97: // id: "endswith" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 94: // id: "contains" + case 98: // id: "contains" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 95: // id: "like" + case 99: // id: "like" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 96: // id: "between" + case 100: // id: "between" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 97: // id: "key or value" + case 101: // id: "key or value" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 98: // id: "sort" + case 102: // id: "sort" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 99: // id: "distinct" + case 103: // id: "distinct" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 100: // id: "limit" + case 104: // id: "limit" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; - case 101: // id: "in" + case 105: // id: "in" { yylhs.value.as < std::string > () = yystack_[0].value.as < std::string > (); } break; @@ -2157,148 +2173,156 @@ namespace yy { } - const signed char parser::yypact_ninf_ = -94; + const signed char parser::yypact_ninf_ = -79; const signed char parser::yytable_ninf_ = -1; const short parser::yypact_[] = { - 118, -94, -94, -54, -94, -94, -94, -94, -94, -94, - 118, -94, -94, -94, -94, -94, -94, -94, -94, -94, - -94, -94, -94, -94, 118, 23, -10, -94, 75, 49, - -94, -94, -94, -94, -94, 306, -94, -94, -15, 203, - -94, 118, 118, 9, -94, -94, -94, -94, -94, -94, - -94, 190, 190, 190, 190, 154, 190, -94, -94, -94, - -94, -22, 235, 318, -19, -94, -94, -94, -94, -94, - -94, -94, -94, -94, -94, -94, -94, -8, -12, 318, - -94, -94, -94, 38, 6, 15, 17, -94, -94, -94, - 190, -27, -94, -27, -94, -94, 190, 48, 48, 284, - -94, 271, -94, 8, 19, -4, -94, 284, 4, -94, - 318, 34, -94, -94, 65, 12, 48, -3, -94, -94, - 71, 32, -94, 44, -94, -94, 46, -94, -94, -94, - -94, 47, 45, -94, -42, 318, -13, 318, 51, 284, - -94, 83, 50, 318, 118, -94, -94, 3, -94, -94, - 34, -94, -94, -94, 32, -94, -94, -6, 318, -94, - -94, -94, 318, 52, 3, 34, 64, -94, -94 + 126, -79, -79, -31, -79, -79, -79, -79, -79, -79, + 126, -79, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, 126, 314, 34, 13, -79, 282, + 48, -79, -79, -79, -79, -79, -13, 336, -79, -79, + -15, 271, 4, -79, 8, -79, 126, 126, -7, -79, + -79, -79, -79, -79, -79, -79, 198, 198, 198, 198, + 162, 198, -79, -79, -79, -79, -2, 234, -79, 314, + 348, -8, -79, -79, -79, -79, -79, -79, -79, -79, + -79, -79, -79, -79, 16, 18, 348, -79, -79, 314, + -79, -79, 70, -3, 42, 46, -79, -79, -79, 198, + -44, -79, -44, -79, -79, 198, 30, 30, -79, 44, + 270, -79, 17, 49, 50, 12, -79, 314, 51, -79, + 348, 52, -79, -79, -79, 77, -25, 30, -79, -79, + 85, 55, -79, 53, -79, -79, 56, -79, -79, -79, + -79, 54, 57, -79, -38, 348, -37, 348, 59, 93, + 61, 348, 126, -79, -79, 3, -79, -79, 52, -79, + -79, 55, -79, -79, -6, 348, -79, -79, -79, 348, + 62, 3, 52, 74, -79, -79 }; const signed char parser::yydefact_[] = { - 87, 64, 65, 0, 60, 61, 62, 66, 67, 68, - 87, 52, 53, 50, 51, 48, 49, 54, 55, 56, - 57, 58, 59, 63, 87, 0, 32, 3, 0, 15, - 22, 29, 21, 8, 87, 0, 87, 6, 0, 0, - 1, 87, 87, 2, 76, 77, 79, 81, 82, 80, - 78, 87, 87, 87, 87, 87, 87, 83, 84, 85, - 86, 0, 87, 0, 69, 90, 92, 93, 94, 95, - 96, 101, 98, 99, 100, 97, 88, 69, 0, 0, - 7, 16, 5, 4, 0, 0, 0, 34, 33, 35, - 87, 19, 15, 20, 17, 18, 87, 9, 11, 0, - 14, 87, 12, 0, 69, 0, 26, 0, 89, 23, - 0, 30, 87, 87, 0, 0, 10, 0, 46, 13, - 0, 89, 25, 0, 70, 71, 0, 72, 73, 74, - 75, 28, 0, 89, 0, 0, 0, 0, 0, 0, - 45, 0, 69, 0, 87, 39, 87, 0, 36, 87, - 37, 42, 47, 91, 0, 24, 27, 0, 0, 43, - 44, 40, 0, 0, 0, 38, 0, 41, 31 + 91, 68, 69, 0, 63, 64, 65, 70, 71, 72, + 91, 55, 56, 53, 54, 51, 52, 57, 58, 59, + 60, 61, 62, 66, 91, 49, 0, 33, 3, 0, + 15, 22, 30, 23, 21, 8, 91, 0, 91, 6, + 0, 0, 0, 48, 0, 1, 91, 91, 2, 80, + 81, 83, 85, 86, 84, 82, 91, 91, 91, 91, + 91, 91, 87, 88, 89, 90, 0, 91, 67, 49, + 0, 73, 94, 96, 97, 98, 99, 100, 105, 102, + 103, 104, 101, 92, 73, 0, 0, 7, 16, 0, + 46, 5, 4, 0, 0, 0, 35, 34, 36, 91, + 19, 15, 20, 17, 18, 91, 9, 11, 14, 0, + 91, 12, 0, 0, 73, 0, 27, 0, 93, 24, + 0, 31, 50, 91, 91, 0, 0, 10, 13, 47, + 0, 93, 26, 0, 74, 75, 0, 76, 77, 78, + 79, 29, 0, 93, 0, 0, 0, 0, 0, 0, + 73, 0, 91, 40, 91, 0, 37, 91, 38, 43, + 95, 0, 25, 28, 0, 0, 44, 45, 41, 0, + 0, 0, 39, 0, 42, 32 }; const signed char parser::yypgoto_[] = { - -94, -94, -9, -94, -17, 0, -94, -94, -94, -94, - -94, -94, -94, -94, -94, -46, -94, -94, -93, -94, - -94, -64, -94, -94, -94, -94, -32, -94, -60 + -79, -79, -9, -79, 1, 0, -79, -79, -79, -79, + -79, -79, -79, -79, -79, -43, 65, 58, -20, -79, + -18, -78, -79, -79, -79, -79, -34, -79, -67 }; const unsigned char parser::yydefgoto_[] = { - 0, 25, 26, 27, 28, 92, 30, 78, 31, 43, - 87, 136, 88, 134, 89, 161, 100, 117, 32, 33, - 34, 106, 131, 55, 56, 62, 35, 76, 77 + 0, 26, 27, 28, 29, 101, 31, 85, 32, 48, + 96, 146, 97, 144, 98, 168, 33, 42, 34, 35, + 36, 116, 141, 60, 61, 67, 37, 83, 84 }; const unsigned char parser::yytable_[] = { - 29, 37, 63, 104, 79, 36, 118, 39, 159, 160, - 29, 41, 42, 109, 126, 38, 41, 42, 145, 111, - 41, 42, 146, 40, 29, 123, 127, 128, 129, 130, - 53, 54, 82, 83, 91, 93, 94, 95, 97, 98, - 122, 29, 29, 99, 105, 80, 152, 148, 124, 125, - 132, 149, 110, 107, 163, 108, 124, 125, 84, 85, - 86, 139, 102, 140, 41, 112, 133, 51, 52, 53, - 54, 120, 81, 115, 113, 147, 114, 150, 155, 116, - 135, 137, 121, 156, 124, 125, 44, 45, 46, 47, - 48, 49, 57, 58, 59, 60, 61, 133, 164, 138, - 123, 119, 165, 51, 52, 53, 54, 141, 142, 144, - 143, 151, 153, 154, 158, 166, 168, 162, 167, 0, - 0, 1, 2, 50, 0, 3, 4, 5, 6, 0, - 51, 52, 53, 54, 0, 157, 7, 8, 9, 0, - 0, 0, 0, 0, 29, 0, 10, 0, 11, 12, + 30, 39, 70, 114, 86, 43, 119, 44, 166, 167, + 30, 46, 47, 58, 59, 40, 7, 8, 9, 121, + 46, 47, 153, 156, 30, 41, 154, 157, 38, 68, + 56, 57, 58, 59, 45, 88, 132, 91, 92, 46, + 47, 133, 93, 94, 95, 87, 30, 30, 109, 43, + 68, 44, 69, 142, 170, 115, 123, 100, 102, 103, + 104, 106, 107, 25, 134, 135, 143, 111, 89, 122, + 90, 44, 162, 137, 138, 139, 140, 117, 155, 118, + 158, 89, 120, 129, 163, 56, 57, 58, 59, 145, + 147, 62, 63, 64, 65, 66, 46, 136, 171, 44, + 126, 124, 172, 134, 135, 125, 127, 134, 135, 69, + 128, 148, 130, 131, 133, 143, 149, 151, 150, 159, + 165, 152, 160, 169, 161, 173, 175, 112, 174, 1, + 2, 108, 0, 3, 4, 5, 6, 0, 0, 0, + 0, 0, 0, 164, 7, 8, 9, 0, 0, 0, + 0, 0, 30, 0, 10, 0, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 3, + 4, 5, 6, 0, 0, 0, 0, 0, 0, 105, + 7, 8, 9, 0, 0, 24, 0, 0, 0, 0, + 0, 25, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 3, 4, 5, 6, 0, + 0, 0, 0, 0, 0, 0, 7, 8, 9, 0, + 0, 99, 0, 0, 0, 0, 0, 25, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 3, 4, 5, 6, 0, 0, 0, 0, 0, - 0, 96, 7, 8, 9, 0, 0, 24, 0, 0, - 0, 0, 0, 0, 11, 12, 13, 14, 15, 16, + 0, 110, 7, 8, 9, 0, 0, 99, 0, 0, + 0, 0, 0, 25, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 3, 4, 5, - 6, 0, 0, 0, 0, 0, 0, 0, 7, 8, - 9, 0, 0, 90, 44, 45, 46, 47, 48, 49, + 6, 0, 49, 50, 51, 52, 53, 54, 7, 8, + 9, 0, 0, 49, 50, 51, 52, 53, 54, 25, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, - 21, 22, 23, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 3, 4, 5, 6, 0, 0, 0, 90, - 0, 50, 101, 7, 8, 9, 0, 0, 51, 52, - 53, 54, 0, 81, 0, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 3, 4, - 5, 6, 0, 0, 0, 0, 0, 0, 0, 7, - 8, 9, 4, 5, 6, 0, 0, 0, 0, 0, - 0, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 22, 23, 11, 12, 13, 14, 15, 16, - 17, 18, 19, 20, 21, 22, 23, 64, 0, 0, - 0, 0, 0, 0, 0, 65, 0, 0, 0, 103, - 0, 0, 0, 0, 0, 0, 0, 65, 0, 66, - 67, 68, 69, 70, 71, 72, 73, 74, 0, 0, - 75, 66, 67, 68, 69, 70, 71, 72, 73, 74, - 0, 0, 75 + 21, 22, 23, 0, 0, 0, 0, 0, 0, 55, + 0, 0, 4, 5, 6, 0, 56, 57, 58, 59, + 55, 88, 7, 8, 9, 25, 0, 56, 57, 58, + 59, 0, 0, 0, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 71, 0, 0, + 0, 0, 0, 0, 0, 72, 0, 0, 0, 113, + 0, 0, 0, 0, 0, 0, 0, 72, 0, 73, + 74, 75, 76, 77, 78, 79, 80, 81, 0, 0, + 82, 73, 74, 75, 76, 77, 78, 79, 80, 81, + 0, 0, 82 }; const short parser::yycheck_[] = { - 0, 10, 34, 63, 36, 59, 99, 24, 5, 6, - 10, 26, 27, 77, 107, 24, 26, 27, 60, 79, - 26, 27, 64, 0, 24, 29, 22, 23, 24, 25, - 57, 58, 41, 42, 51, 52, 53, 54, 55, 56, - 104, 41, 42, 65, 63, 60, 139, 60, 52, 53, - 110, 64, 64, 61, 60, 63, 52, 53, 49, 50, - 51, 64, 62, 66, 26, 59, 63, 55, 56, 57, - 58, 63, 60, 90, 59, 135, 59, 137, 142, 96, - 112, 113, 63, 143, 52, 53, 11, 12, 13, 14, - 15, 16, 43, 44, 45, 46, 47, 63, 158, 34, - 29, 101, 162, 55, 56, 57, 58, 63, 62, 64, - 63, 60, 29, 63, 146, 63, 52, 149, 164, -1, - -1, 3, 4, 48, -1, 7, 8, 9, 10, -1, - 55, 56, 57, 58, -1, 144, 18, 19, 20, -1, - -1, -1, -1, -1, 144, -1, 28, -1, 30, 31, + 0, 10, 36, 70, 38, 25, 84, 25, 5, 6, + 10, 26, 27, 57, 58, 24, 18, 19, 20, 86, + 26, 27, 60, 60, 24, 24, 64, 64, 59, 42, + 55, 56, 57, 58, 0, 60, 114, 46, 47, 26, + 27, 29, 49, 50, 51, 60, 46, 47, 66, 69, + 42, 69, 65, 120, 60, 63, 59, 56, 57, 58, + 59, 60, 61, 65, 52, 53, 63, 67, 64, 89, + 66, 89, 150, 22, 23, 24, 25, 61, 145, 63, + 147, 64, 64, 66, 151, 55, 56, 57, 58, 123, + 124, 43, 44, 45, 46, 47, 26, 117, 165, 117, + 99, 59, 169, 52, 53, 59, 105, 52, 53, 65, + 110, 34, 63, 63, 29, 63, 63, 63, 62, 60, + 154, 64, 29, 157, 63, 63, 52, 69, 171, 3, + 4, 66, -1, 7, 8, 9, 10, -1, -1, -1, + -1, -1, -1, 152, 18, 19, 20, -1, -1, -1, + -1, -1, 152, -1, 28, -1, 30, 31, 32, 33, + 34, 35, 36, 37, 38, 39, 40, 41, 42, 7, + 8, 9, 10, -1, -1, -1, -1, -1, -1, 17, + 18, 19, 20, -1, -1, 59, -1, -1, -1, -1, + -1, 65, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 7, 8, 9, 10, -1, + -1, -1, -1, -1, -1, -1, 18, 19, 20, -1, + -1, 59, -1, -1, -1, -1, -1, 65, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 7, 8, 9, 10, -1, -1, -1, -1, -1, -1, 17, 18, 19, 20, -1, -1, 59, -1, -1, - -1, -1, -1, -1, 30, 31, 32, 33, 34, 35, + -1, -1, -1, 65, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 7, 8, 9, - 10, -1, -1, -1, -1, -1, -1, -1, 18, 19, - 20, -1, -1, 59, 11, 12, 13, 14, 15, 16, + 10, -1, 11, 12, 13, 14, 15, 16, 18, 19, + 20, -1, -1, 11, 12, 13, 14, 15, 16, 65, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, -1, -1, -1, -1, -1, -1, -1, - -1, -1, 7, 8, 9, 10, -1, -1, -1, 59, - -1, 48, 17, 18, 19, 20, -1, -1, 55, 56, - 57, 58, -1, 60, -1, 30, 31, 32, 33, 34, - 35, 36, 37, 38, 39, 40, 41, 42, 7, 8, - 9, 10, -1, -1, -1, -1, -1, -1, -1, 18, - 19, 20, 8, 9, 10, -1, -1, -1, -1, -1, - -1, 30, 31, 32, 33, 34, 35, 36, 37, 38, - 39, 40, 41, 42, 30, 31, 32, 33, 34, 35, + 40, 41, 42, -1, -1, -1, -1, -1, -1, 48, + -1, -1, 8, 9, 10, -1, 55, 56, 57, 58, + 48, 60, 18, 19, 20, 65, -1, 55, 56, 57, + 58, -1, -1, -1, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 21, -1, -1, -1, -1, -1, -1, -1, 29, -1, -1, -1, 21, -1, -1, -1, -1, -1, -1, -1, 29, -1, 43, @@ -2312,21 +2336,22 @@ namespace yy { { 0, 3, 4, 7, 8, 9, 10, 18, 19, 20, 28, 30, 31, 32, 33, 34, 35, 36, 37, 38, - 39, 40, 41, 42, 59, 68, 69, 70, 71, 72, - 73, 75, 85, 86, 87, 93, 59, 69, 69, 71, - 0, 26, 27, 76, 11, 12, 13, 14, 15, 16, - 48, 55, 56, 57, 58, 90, 91, 43, 44, 45, - 46, 47, 92, 93, 21, 29, 43, 44, 45, 46, - 47, 48, 49, 50, 51, 54, 94, 95, 74, 93, - 60, 60, 69, 69, 49, 50, 51, 77, 79, 81, - 59, 71, 72, 71, 71, 71, 17, 71, 71, 65, - 83, 17, 72, 21, 95, 63, 88, 61, 63, 88, - 64, 95, 59, 59, 59, 71, 71, 84, 85, 72, + 39, 40, 41, 42, 59, 65, 68, 69, 70, 71, + 72, 73, 75, 83, 85, 86, 87, 93, 59, 69, + 69, 71, 84, 85, 87, 0, 26, 27, 76, 11, + 12, 13, 14, 15, 16, 48, 55, 56, 57, 58, + 90, 91, 43, 44, 45, 46, 47, 92, 42, 65, + 93, 21, 29, 43, 44, 45, 46, 47, 48, 49, + 50, 51, 54, 94, 95, 74, 93, 60, 60, 64, + 66, 69, 69, 49, 50, 51, 77, 79, 81, 59, + 71, 72, 71, 71, 71, 17, 71, 71, 83, 87, + 17, 72, 84, 21, 95, 63, 88, 61, 63, 88, + 64, 95, 85, 59, 59, 59, 71, 71, 72, 66, 63, 63, 88, 29, 52, 53, 85, 22, 23, 24, - 25, 89, 95, 63, 80, 93, 78, 93, 34, 64, - 66, 63, 62, 63, 64, 60, 64, 95, 60, 64, - 95, 60, 85, 29, 63, 88, 95, 69, 93, 5, - 6, 82, 93, 60, 95, 95, 63, 82, 52 + 25, 89, 95, 63, 80, 93, 78, 93, 34, 63, + 62, 63, 64, 60, 64, 95, 60, 64, 95, 60, + 29, 63, 88, 95, 69, 93, 5, 6, 82, 93, + 60, 95, 95, 63, 82, 52 }; const signed char @@ -2334,15 +2359,15 @@ namespace yy { { 0, 67, 68, 69, 69, 69, 69, 69, 69, 70, 70, 70, 70, 70, 70, 71, 71, 71, 71, 71, - 71, 72, 72, 73, 73, 73, 73, 73, 73, 73, - 74, 75, 76, 76, 76, 76, 77, 78, 78, 79, - 80, 80, 81, 82, 82, 83, 84, 84, 85, 85, - 85, 85, 85, 85, 85, 85, 85, 85, 85, 85, - 85, 85, 85, 85, 86, 86, 87, 87, 87, 88, - 88, 88, 89, 89, 89, 89, 90, 90, 90, 91, - 91, 91, 91, 92, 92, 92, 92, 93, 93, 94, - 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, - 95, 95 + 71, 72, 72, 72, 73, 73, 73, 73, 73, 73, + 73, 74, 75, 76, 76, 76, 76, 77, 78, 78, + 79, 80, 80, 81, 82, 82, 83, 83, 84, 84, + 84, 85, 85, 85, 85, 85, 85, 85, 85, 85, + 85, 85, 85, 85, 85, 85, 85, 85, 86, 86, + 87, 87, 87, 88, 88, 88, 89, 89, 89, 89, + 90, 90, 90, 91, 91, 91, 91, 92, 92, 92, + 92, 93, 93, 94, 95, 95, 95, 95, 95, 95, + 95, 95, 95, 95, 95, 95 }; const signed char @@ -2350,15 +2375,15 @@ namespace yy { { 0, 2, 2, 1, 3, 3, 2, 3, 1, 3, 4, 3, 3, 4, 3, 1, 3, 3, 3, 3, - 3, 1, 1, 3, 6, 4, 3, 6, 4, 1, - 2, 10, 0, 2, 2, 2, 4, 2, 4, 4, - 3, 5, 4, 1, 1, 3, 1, 3, 1, 1, + 3, 1, 1, 1, 3, 6, 4, 3, 6, 4, + 1, 2, 10, 0, 2, 2, 2, 4, 2, 4, + 4, 3, 5, 4, 1, 1, 3, 4, 1, 0, + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, + 1, 1, 1, 0, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 0, 2, 2, - 1, 5, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1 + 1, 0, 2, 2, 1, 5, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1 }; @@ -2396,15 +2421,15 @@ namespace yy { { 0, 148, 148, 151, 152, 153, 154, 155, 156, 159, 160, 165, 166, 167, 172, 175, 176, 177, 178, 179, - 180, 183, 184, 188, 189, 190, 191, 192, 193, 194, - 197, 200, 203, 204, 205, 206, 208, 211, 212, 214, - 217, 218, 220, 223, 224, 226, 230, 231, 234, 235, - 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, - 246, 247, 248, 249, 252, 253, 256, 257, 258, 261, - 262, 263, 266, 267, 268, 269, 272, 273, 274, 277, - 278, 279, 280, 283, 284, 285, 286, 289, 290, 293, - 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, - 306, 307 + 180, 183, 184, 185, 188, 189, 190, 191, 192, 193, + 194, 197, 200, 203, 204, 205, 206, 208, 211, 212, + 214, 217, 218, 220, 223, 224, 226, 227, 230, 231, + 232, 235, 236, 237, 238, 239, 240, 241, 242, 243, + 244, 245, 246, 247, 248, 249, 250, 251, 255, 256, + 259, 260, 261, 264, 265, 266, 269, 270, 271, 272, + 275, 276, 277, 280, 281, 282, 283, 286, 287, 288, + 289, 292, 293, 296, 299, 300, 301, 302, 303, 304, + 305, 306, 307, 308, 309, 310 }; void diff --git a/src/realm/parser/generated/query_bison.hpp b/src/realm/parser/generated/query_bison.hpp index 0ceffbdbd2d..fb6651c96a8 100644 --- a/src/realm/parser/generated/query_bison.hpp +++ b/src/realm/parser/generated/query_bison.hpp @@ -2552,9 +2552,9 @@ switch (yykind) /// Constants. enum { - yylast_ = 372, ///< Last index in yytable_. + yylast_ = 402, ///< Last index in yytable_. yynnts_ = 29, ///< Number of nonterminal symbols. - yyfinal_ = 40 ///< Termination state number. + yyfinal_ = 45 ///< Termination state number. }; diff --git a/src/realm/parser/generated/query_flex.cpp b/src/realm/parser/generated/query_flex.cpp index a1498543e01..e371d8b8c3c 100644 --- a/src/realm/parser/generated/query_flex.cpp +++ b/src/realm/parser/generated/query_flex.cpp @@ -101,7 +101,6 @@ typedef int16_t flex_int16_t; typedef uint16_t flex_uint16_t; typedef int32_t flex_int32_t; typedef uint32_t flex_uint32_t; -typedef uint64_t flex_uint64_t; #else typedef signed char flex_int8_t; typedef short int flex_int16_t; @@ -331,7 +330,7 @@ struct yy_buffer_state /* Number of characters read into yy_ch_buf, not including EOB * characters. */ - yy_size_t yy_n_chars; + int yy_n_chars; /* Whether we "own" the buffer - i.e., we know we created it, * and can realloc() it to grow it, and should free() it to @@ -429,7 +428,7 @@ static void yy_init_buffer ( YY_BUFFER_STATE b, FILE *file , yyscan_t yyscanner YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); -YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner ); /* %endif */ @@ -496,7 +495,7 @@ static void yynoreturn yy_fatal_error ( const char* msg , yyscan_t yyscanner ); #define YY_DO_BEFORE_ACTION \ yyg->yytext_ptr = yy_bp; \ /* %% [2.0] code to fiddle yytext and yyleng for yymore() goes here \ */\ - yyleng = (yy_size_t) (yy_cp - yy_bp); \ + yyleng = (int) (yy_cp - yy_bp); \ yyg->yy_hold_char = *yy_cp; \ *yy_cp = '\0'; \ /* %% [3.0] code to copy yytext_ptr to yytext[] goes here, if %array \ */\ @@ -1196,8 +1195,8 @@ struct yyguts_t size_t yy_buffer_stack_max; /**< capacity of stack. */ YY_BUFFER_STATE * yy_buffer_stack; /**< Stack as an array. */ char yy_hold_char; - yy_size_t yy_n_chars; - yy_size_t yyleng_r; + int yy_n_chars; + int yyleng_r; char *yy_c_buf_p; int yy_init; int yy_start; @@ -1289,7 +1288,7 @@ void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); - yy_size_t yyget_leng ( yyscan_t yyscanner ); + int yyget_leng ( yyscan_t yyscanner ); @@ -1403,7 +1402,7 @@ static int input ( yyscan_t yyscanner ); if ( YY_CURRENT_BUFFER_LVALUE->yy_is_interactive ) \ { \ int c = '*'; \ - yy_size_t n; \ + int n; \ for ( n = 0; n < max_size && \ (c = getc( yyin )) != EOF && c != '\n'; ++n ) \ buf[n] = (char) c; \ @@ -2109,7 +2108,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner) else { - yy_size_t num_to_read = + int num_to_read = YY_CURRENT_BUFFER_LVALUE->yy_buf_size - number_to_move - 1; while ( num_to_read <= 0 ) @@ -2123,7 +2122,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner) if ( b->yy_is_our_buffer ) { - yy_size_t new_size = b->yy_buf_size * 2; + int new_size = b->yy_buf_size * 2; if ( new_size <= 0 ) b->yy_buf_size += b->yy_buf_size / 8; @@ -2181,7 +2180,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner) if ((yyg->yy_n_chars + number_to_move) > YY_CURRENT_BUFFER_LVALUE->yy_buf_size) { /* Extend the array by 50%, plus the number we really need. */ - yy_size_t new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); + int new_size = yyg->yy_n_chars + number_to_move + (yyg->yy_n_chars >> 1); YY_CURRENT_BUFFER_LVALUE->yy_ch_buf = (char *) yyrealloc( (void *) YY_CURRENT_BUFFER_LVALUE->yy_ch_buf, (yy_size_t) new_size , yyscanner ); if ( ! YY_CURRENT_BUFFER_LVALUE->yy_ch_buf ) @@ -2309,7 +2308,7 @@ static int yy_get_next_buffer (yyscan_t yyscanner) else { /* need more input */ - yy_size_t offset = yyg->yy_c_buf_p - yyg->yytext_ptr; + int offset = (int) (yyg->yy_c_buf_p - yyg->yytext_ptr); ++yyg->yy_c_buf_p; switch ( yy_get_next_buffer( yyscanner ) ) @@ -2778,12 +2777,12 @@ YY_BUFFER_STATE yy_scan_string (const char * yystr , yyscan_t yyscanner) * @param yyscanner The scanner object. * @return the newly allocated buffer state object. */ -YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, yy_size_t _yybytes_len , yyscan_t yyscanner) +YY_BUFFER_STATE yy_scan_bytes (const char * yybytes, int _yybytes_len , yyscan_t yyscanner) { YY_BUFFER_STATE b; char *buf; yy_size_t n; - yy_size_t i; + int i; /* Get memory for full buffer, including space for trailing EOB's. */ n = (yy_size_t) (_yybytes_len + 2); @@ -2842,7 +2841,7 @@ static void yynoreturn yy_fatal_error (const char* msg , yyscan_t yyscanner) do \ { \ /* Undo effects of setting up yytext. */ \ - yy_size_t yyless_macro_arg = (n); \ + int yyless_macro_arg = (n); \ YY_LESS_LINENO(yyless_macro_arg);\ yytext[yyleng] = yyg->yy_hold_char; \ yyg->yy_c_buf_p = yytext + yyless_macro_arg; \ @@ -2930,7 +2929,7 @@ FILE *yyget_out (yyscan_t yyscanner) /** Get the length of the current token. * @param yyscanner The scanner object. */ -yy_size_t yyget_leng (yyscan_t yyscanner) +int yyget_leng (yyscan_t yyscanner) { struct yyguts_t * yyg = (struct yyguts_t*)yyscanner; return yyleng; diff --git a/src/realm/parser/generated/query_flex.hpp b/src/realm/parser/generated/query_flex.hpp index ccb937ad959..29b3ba5916f 100644 --- a/src/realm/parser/generated/query_flex.hpp +++ b/src/realm/parser/generated/query_flex.hpp @@ -100,7 +100,6 @@ typedef int16_t flex_int16_t; typedef uint16_t flex_uint16_t; typedef int32_t flex_int32_t; typedef uint32_t flex_uint32_t; -typedef uint64_t flex_uint64_t; #else typedef signed char flex_int8_t; typedef short int flex_int16_t; @@ -279,7 +278,7 @@ struct yy_buffer_state /* Number of characters read into yy_ch_buf, not including EOB * characters. */ - yy_size_t yy_n_chars; + int yy_n_chars; /* Whether we "own" the buffer - i.e., we know we created it, * and can realloc() it to grow it, and should free() it to @@ -340,7 +339,7 @@ void yypop_buffer_state ( yyscan_t yyscanner ); YY_BUFFER_STATE yy_scan_buffer ( char *base, yy_size_t size , yyscan_t yyscanner ); YY_BUFFER_STATE yy_scan_string ( const char *yy_str , yyscan_t yyscanner ); -YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, yy_size_t len , yyscan_t yyscanner ); +YY_BUFFER_STATE yy_scan_bytes ( const char *bytes, int len , yyscan_t yyscanner ); /* %endif */ @@ -460,7 +459,7 @@ void yyset_out ( FILE * _out_str , yyscan_t yyscanner ); - yy_size_t yyget_leng ( yyscan_t yyscanner ); + int yyget_leng ( yyscan_t yyscanner ); diff --git a/src/realm/parser/query_bison.yy b/src/realm/parser/query_bison.yy index a90a17271c1..f413f6070ba 100644 --- a/src/realm/parser/query_bison.yy +++ b/src/realm/parser/query_bison.yy @@ -182,7 +182,7 @@ expr value : constant { $$ = drv.m_parse_nodes.create($1);} | prop { $$ = drv.m_parse_nodes.create($1);} - + | list { $$ = drv.m_parse_nodes.create($1);} prop : path id post_op { $$ = drv.m_parse_nodes.create($1, $2, $3); } @@ -223,11 +223,12 @@ direction : ASCENDING { $$ = true; } | DESCENDING { $$ = false; } -list : '{' list_content '}' { $$ = $2; } - +list : '{' list_content '}' { $$ = $2; } + | comp_type '{' list_content '}' { $3->set_comp_type(ExpressionComparisonType($1)); $$ = $3; } list_content : constant { $$ = drv.m_parse_nodes.create($1); } + | %empty { $$ = drv.m_parse_nodes.create(); } | list_content ',' constant { $1->add_element($3); $$ = $1; } constant @@ -247,6 +248,8 @@ constant | FALSE { $$ = drv.m_parse_nodes.create(ConstantNode::FALSE, ""); } | NULL_VAL { $$ = drv.m_parse_nodes.create(ConstantNode::NULL_VAL, ""); } | ARG { $$ = drv.m_parse_nodes.create(ConstantNode::ARG, $1); } + | comp_type ARG { $$ = drv.m_parse_nodes.create(ExpressionComparisonType($1), $2); } + boolexpr : "truepredicate" { $$ = drv.m_parse_nodes.create(true); } diff --git a/src/realm/parser/query_parser.hpp b/src/realm/parser/query_parser.hpp index 740a99d5ee1..304073f793b 100644 --- a/src/realm/parser/query_parser.hpp +++ b/src/realm/parser/query_parser.hpp @@ -63,6 +63,16 @@ struct AnyContext { } return false; } + bool is_list(const util::Any& wrapper) + { + if (!wrapper.has_value()) { + return false; + } + if (wrapper.type() == typeid(std::vector)) { + return true; + } + return false; + } DataType get_type_of(const util::Any& wrapper) { const std::type_info& type{wrapper.type()}; @@ -128,7 +138,9 @@ class Arguments { virtual Decimal128 decimal128_for_argument(size_t argument_index) = 0; virtual UUID uuid_for_argument(size_t argument_index) = 0; virtual ObjLink objlink_for_argument(size_t argument_index) = 0; + virtual std::vector list_for_argument(size_t argument_index) = 0; virtual bool is_argument_null(size_t argument_index) = 0; + virtual bool is_argument_list(size_t argument_index) = 0; virtual DataType type_for_argument(size_t argument_index) = 0; size_t get_num_args() const { @@ -137,7 +149,7 @@ class Arguments { // dynamic conversion space with lifetime tied to this // it is used for storing literal binary/string data - std::vector buffer_space; + std::vector buffer_space; protected: void verify_ndx(size_t ndx) const @@ -216,6 +228,14 @@ class ArgumentConverter : public Arguments { { return get(i); } + std::vector list_for_argument(size_t i) override + { + return get>(i); + } + bool is_argument_list(size_t i) override + { + return m_ctx.is_list(at(i)); + } bool is_argument_null(size_t i) override { return m_ctx.is_null(at(i)); @@ -305,6 +325,14 @@ class NoArguments : public Arguments { { throw NoArgsError(); } + bool is_argument_list(size_t) + { + throw NoArgsError(); + } + std::vector list_for_argument(size_t) + { + throw NoArgsError(); + } bool is_argument_null(size_t) { throw NoArgsError(); diff --git a/src/realm/query_expression.cpp b/src/realm/query_expression.cpp index e07b97dbabf..a03c9affc18 100644 --- a/src/realm/query_expression.cpp +++ b/src/realm/query_expression.cpp @@ -414,7 +414,7 @@ class DictionarySize : public Columns { Allocator& alloc = this->m_link_map.get_target_table()->get_alloc(); Value list_refs; this->get_lists(index, list_refs, 1); - destination.init(list_refs.m_from_link_list, list_refs.size()); + destination.init(list_refs.m_from_list, list_refs.size()); for (size_t i = 0; i < list_refs.size(); i++) { ref_type ref = to_ref(list_refs[i].get_int()); size_t s = ClusterTree::size_from_ref(ref, alloc); diff --git a/src/realm/query_expression.hpp b/src/realm/query_expression.hpp index 1589bb78e7f..f81fdb85f13 100644 --- a/src/realm/query_expression.hpp +++ b/src/realm/query_expression.hpp @@ -239,7 +239,7 @@ class ValueBase { using ValueType = QueryValue; static const size_t chunk_size = 8; - bool m_from_link_list = false; + bool m_from_list = false; ValueBase() = default; ValueBase(const ValueType& init_val) @@ -257,7 +257,7 @@ class ValueBase { ValueBase& operator=(const ValueBase& other) { - m_from_link_list = other.m_from_link_list; + m_from_list = other.m_from_list; set(other.begin(), other.end()); return *this; } @@ -269,7 +269,7 @@ class ValueBase { void init(bool from_link_list, size_t nb_values) { - m_from_link_list = from_link_list; + m_from_list = from_link_list; resize(nb_values); } @@ -351,7 +351,7 @@ class ValueBase { TOperator o; // Operate on values one-by-one size_t sz = right.size(); - init(right.m_from_link_list, sz); + init(right.m_from_list, sz); for (size_t i = 0; i < sz; i++) { set(i, o(const_value, right[i])); } @@ -362,7 +362,7 @@ class ValueBase { TOperator o; // Operate on values one-by-one size_t sz = left.size(); - init(left.m_from_link_list, sz); + init(left.m_from_list, sz); for (size_t i = 0; i < sz; i++) { set(i, o(left[i], const_value)); } @@ -372,7 +372,7 @@ class ValueBase { { TOperator o; - if (!left.m_from_link_list && !right.m_from_link_list) { + if (!left.m_from_list && !right.m_from_list) { // Operate on values one-by-one (one value is one row; no links) size_t min = std::min(left.size(), right.size()); init(false, min); @@ -381,11 +381,12 @@ class ValueBase { set(i, o(left[i], right[i])); } } - else if (left.m_from_link_list && right.m_from_link_list) { + else if (left.m_from_list && right.m_from_list) { // FIXME: Many-to-many links not supported yet. Need to specify behaviour - REALM_ASSERT_DEBUG(false); + // Eg: `{1, 2, 3} * {4, 5} > age` + throw std::logic_error("Operations involving two lists are not supported"); } - else if (!left.m_from_link_list && right.m_from_link_list) { + else if (!left.m_from_list && right.m_from_list) { // Right values come from link. Left must come from single row. REALM_ASSERT_DEBUG(left.size() > 0); init(true, right.size()); @@ -395,7 +396,7 @@ class ValueBase { set(i, o(left_value, right[i])); } } - else if (left.m_from_link_list && !right.m_from_link_list) { + else if (left.m_from_list && !right.m_from_list) { // Same as above, but with left values coming from links REALM_ASSERT_DEBUG(right.size() > 0); init(true, left.size()); @@ -414,9 +415,9 @@ class ValueBase { { TCond c; const size_t sz = right.size(); - if (!right.m_from_link_list) { - REALM_ASSERT_DEBUG(comparison == - ExpressionComparisonType::Any); // ALL/NONE not supported for non list types + if (!right.m_from_list) { + // ALL/NONE not supported for non list types + REALM_ASSERT_DEBUG(comparison == ExpressionComparisonType::Any); for (size_t m = 0; m < sz; m++) { if (c(left, right[m])) return m; @@ -453,7 +454,7 @@ class ValueBase { { TCond c; const size_t sz = left.size(); - if (!left.m_from_link_list) { + if (!left.m_from_list) { REALM_ASSERT_DEBUG(comparison == ExpressionComparisonType::Any); // ALL/NONE not supported for non list types for (size_t m = 0; m < sz; m++) { @@ -487,80 +488,188 @@ class ValueBase { // Given a TCond (==, !=, >, <, >=, <=) and two Value, return index of first match template - REALM_FORCEINLINE static size_t compare(const ValueBase& left, const ValueBase& right, - ExpressionComparisonType left_cmp_type, - ExpressionComparisonType right_cmp_type) + REALM_FORCEINLINE static size_t compare(ValueBase& left, ValueBase& right, + util::Optional left_cmp_type, + util::Optional right_cmp_type) { TCond c; + using Compare = ExpressionComparisonType; - if (!left.m_from_link_list && !right.m_from_link_list) { - REALM_ASSERT_DEBUG(left_cmp_type == - ExpressionComparisonType::Any); // ALL/NONE not supported for non list types - REALM_ASSERT_DEBUG(right_cmp_type == - ExpressionComparisonType::Any); // ALL/NONE not supported for non list types + if (!left.m_from_list && !right.m_from_list) { + // ALL/NONE not supported for non list types + REALM_ASSERT_DEBUG(!left_cmp_type || *left_cmp_type == Compare::Any); + REALM_ASSERT_DEBUG(!right_cmp_type || *right_cmp_type == Compare::Any); // Compare values one-by-one (one value is one row; no link lists) size_t min = minimum(left.size(), right.size()); for (size_t m = 0; m < min; m++) { if (c(left[m], right[m])) return m; } + return not_found; } - else if (left.m_from_link_list && right.m_from_link_list) { - // FIXME: Many-to-many links not supported yet. Need to specify behaviour - // knowing the comparison types means we can potentially support things such as: - // ALL list.int > list.[FIRST].int - // ANY list.int > ALL list2.int - // NONE list.int > ANY list2.int - REALM_ASSERT_DEBUG(false); + + if (left.m_from_list && right.m_from_list && !left_cmp_type && !right_cmp_type) { + // Both lists and no ANY, NONE, ALL specified - simple element by element comparison + if (left.size() != right.size()) { + if constexpr (std::is_same_v) { + return 0; // mismatch size + } + else { + return not_found; + } + } + for (size_t i = 0; i < left.size(); ++i) { + if (!c(left[i], right[i])) { + return not_found; + } + } + return 0; // all elements matched in the right order } - else if (!left.m_from_link_list && right.m_from_link_list) { - // Right values come from link list. Left must come from single row. Semantics: Match if at least 1 - // linked-to-value fulfills the condition - REALM_ASSERT_DEBUG(left.size() > 0); - const size_t num_right_values = right.size(); - ValueType left_val = left[0]; - for (size_t r = 0; r < num_right_values; r++) { - bool match = c(left_val, right[r]); - if (match) { - if (right_cmp_type == ExpressionComparisonType::Any) { + + // if one side omitted a comparison type, assume ANY + const Compare compare_left = left_cmp_type.value_or(Compare::Any); + const Compare compare_right = right_cmp_type.value_or(Compare::Any); + + size_t left_size = left.m_from_list ? left.size() : 1; + size_t right_size = right.m_from_list ? right.size() : 1; + + if (left_size > 2 && right_size > 2) { + std::sort(left.begin(), left.end()); + std::sort(right.begin(), right.end()); + + if constexpr (std::is_same_v) { + if (compare_left != ExpressionComparisonType::None && compare_right == Compare::Any) { + // Optimization with O(n) complexity + const bool any = compare_left == ExpressionComparisonType::Any; + size_t left_idx = 0; + size_t right_idx = 0; + while (right_idx < right_size) { + if (c(left[left_idx], right[right_idx])) { + left_idx++; + right_idx++; + if (any || left_idx == left_size) { + return 0; + } + } + else { + if (left[left_idx] < right[right_idx]) { + if (any && left_idx < left_size) { + left_idx++; + } + else { + return not_found; + } + } + else { + right_idx++; + } + } + } + return not_found; + } + } + else if constexpr (realm::is_any_v) { + // Only consider first and last + left[1] = left[left_size - 1]; + left_size = 2; + right[1] = right[right_size - 1]; + right_size = 2; + } + else { + // remove duplicates to reduce comparison time in nested loops + left_size = std::unique(left.begin(), left.end()) - left.begin(); + right_size = std::unique(right.begin(), right.end()) - right.begin(); + } + } + + if constexpr (realm::is_any_v) { + // The string operators have the arguments reversed so we have to iterate right in the + // outer loop as this is actually the left argument + auto left_matches = [&](const QueryValue& right_val) { + for (size_t i = 0; i < left_size; i++) { + if (c(left[i], right_val)) { + // match + if (compare_left == Compare::Any) { + return true; + } + if (compare_left == Compare::None) { + return false; // one matched + } + } + else { + // no match + if (compare_left == Compare::All) { + return false; + } + } + } + if (compare_left == Compare::None || compare_left == Compare::All) { + return true; + } + return false; + }; + + for (size_t i = 0; i < right_size; i++) { + if (left_matches(right[i])) { + if (compare_right == Compare::Any) { return 0; } - if (right_cmp_type == ExpressionComparisonType::None) { + if (compare_right == Compare::None) { return not_found; // one matched } } else { - if (right_cmp_type == ExpressionComparisonType::All) { + if (compare_right == Compare::All) { return not_found; } } } - if (right_cmp_type == ExpressionComparisonType::None || right_cmp_type == ExpressionComparisonType::All) { + if (compare_right == Compare::None || compare_right == Compare::All) { return 0; // either none or all } } - else if (left.m_from_link_list && !right.m_from_link_list) { - // Same as above, but with left values coming from link list. - REALM_ASSERT_DEBUG(right.size() > 0); - const size_t num_left_values = left.size(); - ValueType right_val = right[0]; - for (size_t l = 0; l < num_left_values; l++) { - bool match = c(left[l], right_val); - if (match) { - if (left_cmp_type == ExpressionComparisonType::Any) { + else { + auto right_matches = [&](const QueryValue& left_val) { + for (size_t i = 0; i < right_size; i++) { + if (c(left_val, right[i])) { + // match + if (compare_right == Compare::Any) { + return true; + } + if (compare_right == Compare::None) { + return false; // one matched + } + } + else { + // no match + if (compare_right == Compare::All) { + return false; + } + } + } + if (compare_right == Compare::None || compare_right == Compare::All) { + return true; + } + return false; + }; + + for (size_t i = 0; i < left_size; i++) { + if (right_matches(left[i])) { + if (compare_left == Compare::Any) { return 0; } - if (left_cmp_type == ExpressionComparisonType::None) { + if (compare_left == ExpressionComparisonType::None) { return not_found; // one matched } } else { - if (left_cmp_type == ExpressionComparisonType::All) { + if (compare_left == ExpressionComparisonType::All) { return not_found; } } } - if (left_cmp_type == ExpressionComparisonType::None || left_cmp_type == ExpressionComparisonType::All) { + if (compare_left == ExpressionComparisonType::None || compare_left == ExpressionComparisonType::All) { return 0; // either none or all } } @@ -696,9 +805,9 @@ class Subexpr { return {}; } - virtual ExpressionComparisonType get_comparison_type() const + virtual util::Optional get_comparison_type() const { - return ExpressionComparisonType::Any; + return util::none; } }; @@ -1172,31 +1281,60 @@ class Value : public ValueBase, public Subexpr2 { { } - std::string description(util::serializer::SerialisationState&) const override + std::string value_to_string(size_t ndx) const { - if (ValueBase::m_from_link_list) { - return util::serializer::print_value(util::to_string(ValueBase::size()) + - (ValueBase::size() == 1 ? " value" : " values")); - } - if (size() > 0) { - auto val = get(0); - if (val.is_null()) - return "NULL"; + auto val = get(ndx); + if (val.is_null()) + return "NULL"; + else { + if constexpr (std::is_same_v) { + return util::serializer::print_value(val.get_type_of_value()); + } else { - if constexpr (std::is_same_v) { - return util::serializer::print_value(val.get_type_of_value()); - } - else { - return util::serializer::print_value(val.template get()); + return util::serializer::print_value(val.template get()); + } + } + } + + std::string description(util::serializer::SerialisationState& state) const override + { + const size_t sz = size(); + if (m_from_list) { + std::string desc = state.describe_expression_type(m_comparison_type) + "{"; + for (size_t i = 0; i < sz; ++i) { + if (i != 0) { + desc += ", "; } + desc += value_to_string(i); } + desc += "}"; + return desc; + } + else if (sz == 1) { + return value_to_string(0); } return ""; } + bool has_multiple_values() const override + { + return m_from_list; + } + bool has_constant_evaluation() const override { - return true; + return !m_from_list; + } + + util::Optional get_comparison_type() const final + { + REALM_ASSERT_DEBUG(!m_comparison_type || m_from_list); + return m_comparison_type; + } + + void set_comparison_type(util::Optional type) + { + m_comparison_type = type; } Mixed get_mixed() override @@ -1213,6 +1351,9 @@ class Value : public ValueBase, public Subexpr2 { { return make_subexpr>(*this); } + +protected: + util::Optional m_comparison_type; }; class ConstantMixedValue : public Value { @@ -1557,7 +1698,8 @@ Value make_value_for_link(bool only_unary_links, size_t size) // This class can be used as untyped base for expressions that handle object properties class ObjPropertyBase { public: - ObjPropertyBase(ColKey column, ConstTableRef table, std::vector links, ExpressionComparisonType type) + ObjPropertyBase(ColKey column, ConstTableRef table, std::vector links, + util::Optional type) : m_link_map(table, std::move(links)) , m_column_key(column) , m_comparison_type(type) @@ -1569,7 +1711,7 @@ class ObjPropertyBase { , m_comparison_type(other.m_comparison_type) { } - ObjPropertyBase(ColKey column, const LinkMap& link_map, ExpressionComparisonType type) + ObjPropertyBase(ColKey column, const LinkMap& link_map, util::Optional type) : m_link_map(link_map) , m_column_key(column) , m_comparison_type(type) @@ -1605,7 +1747,7 @@ class ObjPropertyBase { LinkMap m_link_map; // Column index of payload column of m_table mutable ColKey m_column_key; - ExpressionComparisonType m_comparison_type; // Any, All, None + util::Optional m_comparison_type; // Any, All, None }; // Combines Subexpr2 and ObjPropertyBase @@ -1677,12 +1819,12 @@ class ObjPropertyExpr : public Subexpr2, public ObjPropertyBase { m_link_map.collect_dependencies(tables); } - virtual std::string description(util::serializer::SerialisationState& state) const override + std::string description(util::serializer::SerialisationState& state) const override { return state.describe_expression_type(m_comparison_type) + state.describe_columns(m_link_map, m_column_key); } - virtual ExpressionComparisonType get_comparison_type() const final + util::Optional get_comparison_type() const final { return m_comparison_type; } @@ -1706,7 +1848,7 @@ class SimpleQuerySupport : public ObjPropertyExpr { using ObjPropertyExpr::links_exist; SimpleQuerySupport(ColKey column, ConstTableRef table, std::vector links = {}, - ExpressionComparisonType type = ExpressionComparisonType::Any) + util::Optional type = util::none) : ObjPropertyExpr(column, table, std::move(links), type) { } @@ -1733,7 +1875,7 @@ class SimpleQuerySupport : public ObjPropertyExpr { if (m_link_map.only_unary_links()) { REALM_ASSERT(destination.size() == 1); - REALM_ASSERT(!destination.m_from_link_list); + REALM_ASSERT(!destination.m_from_list); destination.set_null(0); auto link_translation_key = this->m_link_map.get_unary_link_or_not_found(index); if (link_translation_key) { @@ -1776,7 +1918,7 @@ class SimpleQuerySupport : public ObjPropertyExpr { // Not a link column REALM_ASSERT(m_leaf_ptr != nullptr); REALM_ASSERT(destination.size() == 1); - REALM_ASSERT(!destination.m_from_link_list); + REALM_ASSERT(!destination.m_from_list); if (m_leaf_ptr->is_null(index)) { destination.set_null(0); } @@ -1921,7 +2063,7 @@ template <> class Columns : public SimpleQuerySupport { public: Columns(ColKey column, ConstTableRef table, std::vector links = {}, - ExpressionComparisonType type = ExpressionComparisonType::Any) + util::Optional type = util::none) : SimpleQuerySupport(column, table, links, type) { } @@ -2260,7 +2402,7 @@ class SizeOperator : public Subexpr2 { m_expr->evaluate(index, v); size_t sz = v.size(); - destination.init(v.m_from_link_list, sz); + destination.init(v.m_from_list, sz); for (size_t i = 0; i < sz; i++) { auto elem = v[i].template get(); @@ -2309,7 +2451,7 @@ class TypeOfValueOperator : public Subexpr2 { { } - ExpressionComparisonType get_comparison_type() const override + util::Optional get_comparison_type() const override { return m_expr->get_comparison_type(); } @@ -2339,7 +2481,7 @@ class TypeOfValueOperator : public Subexpr2 { m_expr->evaluate(index, v); size_t sz = v.size(); - destination.init(v.m_from_link_list, sz); + destination.init(v.m_from_list, sz); for (size_t i = 0; i < sz; i++) { auto elem = v[i].template get(); @@ -2418,7 +2560,7 @@ class Columns : public Subexpr2 { } Columns(ColKey column_key, ConstTableRef table, const std::vector& links = {}, - ExpressionComparisonType type = ExpressionComparisonType::Any) + util::Optional type = util::none) : m_link_map(table, links) , m_comparison_type(type) , m_is_list(column_key.is_list()) @@ -2458,7 +2600,7 @@ class Columns : public Subexpr2 { // no need to pass along m_comparison_type because the only operations supported from // the subsequent SubColumns are aggregate operations such as sum, min, max, avg where // having - REALM_ASSERT_DEBUG(m_comparison_type == ExpressionComparisonType::Any); + REALM_ASSERT_DEBUG(!m_comparison_type); return SubColumns(Columns(column_key, m_link_map.get_target_table()), m_link_map); } @@ -2503,7 +2645,7 @@ class Columns : public Subexpr2 { return state.describe_expression_type(m_comparison_type) + state.describe_columns(m_link_map, ColKey()); } - virtual ExpressionComparisonType get_comparison_type() const override + util::Optional get_comparison_type() const override { return m_comparison_type; } @@ -2517,7 +2659,7 @@ class Columns : public Subexpr2 { private: LinkMap m_link_map; - ExpressionComparisonType m_comparison_type; + util::Optional m_comparison_type; bool m_is_list; friend class Table; friend class LinkChain; @@ -2541,7 +2683,7 @@ class Average; class ColumnListBase { public: ColumnListBase(ColKey column_key, ConstTableRef table, const std::vector& links, - ExpressionComparisonType type = ExpressionComparisonType::Any) + util::Optional type = util::none) : m_column_key(column_key) , m_link_map(table, links) , m_comparison_type(type) @@ -2584,7 +2726,7 @@ class ColumnListBase { LeafCacheStorage m_leaf_cache_storage; LeafPtr m_array_ptr; ArrayInteger* m_leaf_ptr = nullptr; - ExpressionComparisonType m_comparison_type = ExpressionComparisonType::Any; + util::Optional m_comparison_type; }; template @@ -2597,7 +2739,7 @@ template class ColumnsCollection : public Subexpr2, public ColumnListBase { public: ColumnsCollection(ColKey column_key, ConstTableRef table, const std::vector& links = {}, - ExpressionComparisonType type = ExpressionComparisonType::Any) + util::Optional type = util::none) : ColumnListBase(column_key, table, links, type) , m_is_nullable_storage(this->m_column_key.get_attrs().test(col_attr_Nullable)) { @@ -2656,7 +2798,7 @@ class ColumnsCollection : public Subexpr2, public ColumnListBase { return ColumnListBase::description(state); } - ExpressionComparisonType get_comparison_type() const final + util::Optional get_comparison_type() const final { return ColumnListBase::m_comparison_type; } @@ -2828,7 +2970,7 @@ template <> class Columns : public ColumnsCollection { public: Columns(ColKey column, ConstTableRef table, std::vector links = {}, - ExpressionComparisonType type = ExpressionComparisonType::Any) + util::Optional type = util::none) : ColumnsCollection(column, table, std::move(links), type) { m_key_type = m_link_map.get_target_table()->get_dictionary_key_type(column); @@ -2938,7 +3080,7 @@ class ColumnDictionaryKeys : public Subexpr2 { m_link_map.collect_dependencies(tables); } - ExpressionComparisonType get_comparison_type() const final + util::Optional get_comparison_type() const final { return m_comparison_type; } @@ -2969,7 +3111,7 @@ class ColumnDictionaryKeys : public Subexpr2 { DataType m_key_type; ColKey m_column_key; LinkMap m_link_map; - ExpressionComparisonType m_comparison_type = ExpressionComparisonType::Any; + util::Optional m_comparison_type; // Leaf cache @@ -3010,7 +3152,7 @@ class ColumnListSize : public ColumnsCollection { Allocator& alloc = ColumnsCollection::get_alloc(); Value list_refs; this->get_lists(index, list_refs, 1); - destination.init(list_refs.m_from_link_list, list_refs.size()); + destination.init(list_refs.m_from_list, list_refs.size()); for (size_t i = 0; i < list_refs.size(); i++) { ref_type list_ref = to_ref(list_refs[i].get_int()); if (list_ref) { @@ -3103,7 +3245,7 @@ class ColumnListElementLength : public Subexpr2 { return m_list.description(state) + util::serializer::value_separator + "length"; } - virtual ExpressionComparisonType get_comparison_type() const override + util::Optional get_comparison_type() const override { return m_list.get_comparison_type(); } @@ -3208,9 +3350,9 @@ class CollectionColumnAggregate : public Subexpr2 list_refs; m_columns_collection.get_lists(index, list_refs, 1); size_t sz = list_refs.size(); - REALM_ASSERT_DEBUG(sz > 0 || list_refs.m_from_link_list); + REALM_ASSERT_DEBUG(sz > 0 || list_refs.m_from_list); // The result is an aggregate value for each table - destination.init_for_links(!list_refs.m_from_link_list, sz); + destination.init_for_links(!list_refs.m_from_list, sz); for (size_t i = 0; i < list_refs.size(); i++) { auto list_ref = to_ref(list_refs[i].get_int()); Operation op; @@ -3338,7 +3480,7 @@ class Columns : public ObjPropertyExpr { using ObjPropertyBase::is_nullable; Columns(ColKey column, ConstTableRef table, std::vector links = {}, - ExpressionComparisonType type = ExpressionComparisonType::Any) + util::Optional type = util::none) : ObjPropertyExpr(column, table, std::move(links), type) { } @@ -3854,7 +3996,7 @@ class Operator : public Subexpr2 { destination = result; } - virtual std::string description(util::serializer::SerialisationState& state) const override + std::string description(util::serializer::SerialisationState& state) const override { std::string s = "("; if (m_left) { @@ -3868,6 +4010,17 @@ class Operator : public Subexpr2 { return s; } + util::Optional get_comparison_type() const override + { + if (!m_left_is_const) { + return m_left->get_comparison_type(); + } + if (!m_right_is_const) { + return m_right->get_comparison_type(); + } + return util::none; + } + std::unique_ptr clone() const override { return make_subexpr(*this); @@ -3927,7 +4080,7 @@ class Compare : public Expression { // finding all matches up front. Subexpr* column = m_left_is_const ? m_right.get() : m_left.get(); - if (column->has_search_index() && column->get_comparison_type() == ExpressionComparisonType::Any) { + if (column->has_search_index() && *column->get_comparison_type() == ExpressionComparisonType::Any) { if (m_const_value.is_null()) { const ObjPropertyBase* prop = dynamic_cast(m_right.get()); // when checking for null across links, null links are considered matches, @@ -4026,27 +4179,31 @@ class Compare : public Expression { size_t match; ValueBase left; ValueBase right; - const ExpressionComparisonType left_cmp_type = m_left->get_comparison_type(); - const ExpressionComparisonType right_cmp_type = m_right->get_comparison_type(); + const util::Optional left_cmp_type = m_left->get_comparison_type(); + const util::Optional right_cmp_type = m_right->get_comparison_type(); if (m_left_is_const) { + const ExpressionComparisonType right_evaluated_cmp_type = + right_cmp_type.value_or(ExpressionComparisonType::Any); for (; start < end;) { m_right->evaluate(start, right); - match = ValueBase::compare_const(m_const_value, right, right_cmp_type); + match = ValueBase::compare_const(m_const_value, right, right_evaluated_cmp_type); if (match != not_found && match + start < end) return start + match; - size_t rows = right.m_from_link_list ? 1 : right.size(); + size_t rows = right.m_from_list ? 1 : right.size(); start += rows; } } else if (m_right_is_const) { + const ExpressionComparisonType left_evaluated_cmp_type = + left_cmp_type.value_or(ExpressionComparisonType::Any); for (; start < end;) { m_left->evaluate(start, left); - match = ValueBase::compare_const(left, m_const_value, left_cmp_type); + match = ValueBase::compare_const(left, m_const_value, left_evaluated_cmp_type); if (match != not_found && match + start < end) return start + match; - size_t rows = left.m_from_link_list ? 1 : left.size(); + size_t rows = left.m_from_list ? 1 : left.size(); start += rows; } } @@ -4058,8 +4215,7 @@ class Compare : public Expression { if (match != not_found && match + start < end) return start + match; - size_t rows = - (left.m_from_link_list || right.m_from_link_list) ? 1 : minimum(right.size(), left.size()); + size_t rows = (left.m_from_list || right.m_from_list) ? 1 : minimum(right.size(), left.size()); start += rows; } } @@ -4069,15 +4225,17 @@ class Compare : public Expression { virtual std::string description(util::serializer::SerialisationState& state) const override { - if (realm::is_any_v) { + if constexpr (realm::is_any_v) { // these string conditions have the arguments reversed but the order is important // operations ==, and != can be reversed because the produce the same results both ways return util::serializer::print_value(m_right->description(state) + " " + TCond::description() + " " + m_left->description(state)); } - return util::serializer::print_value(m_left->description(state) + " " + TCond::description() + " " + - m_right->description(state)); + else { + return util::serializer::print_value(m_left->description(state) + " " + TCond::description() + " " + + m_right->description(state)); + } } std::unique_ptr clone() const override diff --git a/src/realm/table.hpp b/src/realm/table.hpp index 25bfb682eb3..dace3ebbbde 100644 --- a/src/realm/table.hpp +++ b/src/realm/table.hpp @@ -215,7 +215,7 @@ class Table { size_t get_num_unique_values(ColKey col_key) const; template - Columns column(ColKey col_key, ExpressionComparisonType = ExpressionComparisonType::Any) const; + Columns column(ColKey col_key, util::Optional = util::none) const; template Columns column(const Table& origin, ColKey origin_col_key) const; @@ -560,9 +560,12 @@ class Table { } Query where(const DictionaryLinkValues& dictionary_of_links) const; - Query query(const std::string& query_string, const std::vector& arguments = {}) const; + Query query(const std::string& query_string, const std::vector>& arguments = {}) const; + Query query(const std::string& query_string, const std::vector& arguments) const; Query query(const std::string& query_string, const std::vector& arguments, const query_parser::KeyPathMapping& mapping) const; + Query query(const std::string& query_string, const std::vector>& arguments, + const query_parser::KeyPathMapping& mapping) const; Query query(const std::string& query_string, query_parser::Arguments& arguments, const query_parser::KeyPathMapping&) const; @@ -974,7 +977,7 @@ class ColKeys { // It has member functions corresponding to the ones defined on Table. class LinkChain { public: - LinkChain(ConstTableRef t = {}, ExpressionComparisonType type = ExpressionComparisonType::Any) + LinkChain(ConstTableRef t = {}, util::Optional type = util::none) : m_current_table(t) , m_base_table(t) , m_comparison_type(type) @@ -1081,7 +1084,7 @@ class LinkChain { std::vector m_link_cols; ConstTableRef m_current_table; ConstTableRef m_base_table; - ExpressionComparisonType m_comparison_type; + util::Optional m_comparison_type; void add(ColKey ck); @@ -1258,7 +1261,7 @@ inline Allocator& Table::get_alloc() const // For use by queries template -inline Columns Table::column(ColKey col_key, ExpressionComparisonType cmp_type) const +inline Columns Table::column(ColKey col_key, util::Optional cmp_type) const { LinkChain lc(m_own_ref, cmp_type); return lc.column(col_key); diff --git a/src/realm/util/serializer.cpp b/src/realm/util/serializer.cpp index cc207896387..4c849d83967 100644 --- a/src/realm/util/serializer.cpp +++ b/src/realm/util/serializer.cpp @@ -320,17 +320,18 @@ std::string SerialisationState::describe_columns(const LinkMap& link_map, ColKey return desc; } -std::string SerialisationState::describe_expression_type(ExpressionComparisonType type) +std::string SerialisationState::describe_expression_type(util::Optional type) { - switch (type) { - case ExpressionComparisonType::Any: - return ""; // ANY is implied - case ExpressionComparisonType::All: - return "ALL "; - case ExpressionComparisonType::None: - return "NONE "; + if (type) { + switch (*type) { + case ExpressionComparisonType::Any: + return "ANY "; + case ExpressionComparisonType::All: + return "ALL "; + case ExpressionComparisonType::None: + return "NONE "; + } } - REALM_UNREACHABLE(); return ""; } diff --git a/src/realm/util/serializer.hpp b/src/realm/util/serializer.hpp index 4216e71ce92..cd18654750f 100644 --- a/src/realm/util/serializer.hpp +++ b/src/realm/util/serializer.hpp @@ -103,7 +103,7 @@ struct SerialisationState { } std::string describe_column(ConstTableRef table, ColKey col_key); std::string describe_columns(const LinkMap& link_map, ColKey target_col_key); - std::string describe_expression_type(ExpressionComparisonType type); + std::string describe_expression_type(util::Optional type); std::string get_column_name(ConstTableRef table, ColKey col_key); std::string get_backlink_column_name(ConstTableRef from, ColKey col_key); std::string get_variable_name(ConstTableRef table); diff --git a/test/object-store/c_api/c_api.cpp b/test/object-store/c_api/c_api.cpp index 3b9aa7cd782..84e8ce91fc6 100644 --- a/test/object-store/c_api/c_api.cpp +++ b/test/object-store/c_api/c_api.cpp @@ -1751,9 +1751,13 @@ TEST_CASE("C API", "[c_api]") { } SECTION("query") { - auto arg = rlm_str_val("Hello, World!"); - auto q = - cptr_checked(realm_query_parse(realm, class_foo.key, "string == $0 SORT(int ASCENDING)", 1, &arg)); + realm_value_t arg_data[1] = {rlm_str_val("Hello, World!")}; + size_t num_args = 2; + realm_query_arg_t args[2] = {realm_query_arg_t{1, false, &arg_data[0]}, + realm_query_arg_t{1, false, &int_val2}}; + realm_query_arg_t* arg_list = &args[0]; + auto q = cptr_checked( + realm_query_parse(realm, class_foo.key, "string == $0 SORT(int ASCENDING)", 1, arg_list)); SECTION("realm_query_description()") { const char* descr = realm_query_get_description(q.get()); @@ -1773,20 +1777,23 @@ TEST_CASE("C API", "[c_api]") { CHECK(count == count2); } SECTION("realm_query_append_query") { - auto q2 = cptr_checked(realm_query_append_query(q.get(), "TRUEPREDICATE LIMIT(1)", 1, &arg)); + auto q2 = + cptr_checked(realm_query_append_query(q.get(), "TRUEPREDICATE LIMIT(1)", num_args, arg_list)); size_t count; CHECK(checked(realm_query_count(q2.get(), &count))); CHECK(count == 1); - q2 = cptr_checked(realm_query_append_query(q.get(), "FALSEPREDICATE", 1, &arg)); + q2 = cptr_checked(realm_query_append_query(q.get(), "FALSEPREDICATE", num_args, arg_list)); CHECK(checked(realm_query_count(q2.get(), &count))); CHECK(count == 0); - q2 = cptr_checked(realm_query_append_query(q.get(), "TRUEPREDICATE LIMIT(0)", 1, &arg)); + q2 = + cptr_checked(realm_query_append_query(q.get(), "TRUEPREDICATE LIMIT(0)", num_args, arg_list)); CHECK(checked(realm_query_count(q2.get(), &count))); CHECK(count == 0); - q2 = cptr_checked(realm_query_append_query(q.get(), "TRUEPREDICATE LIMIT(10)", 1, &arg)); + q2 = cptr_checked( + realm_query_append_query(q.get(), "TRUEPREDICATE LIMIT(10)", num_args, arg_list)); CHECK(checked(realm_query_count(q2.get(), &count))); CHECK(count == 1); - q2 = cptr_checked(realm_query_append_query(q.get(), "int == $0", 1, &int_val2)); + q2 = cptr_checked(realm_query_append_query(q.get(), "int == $1", num_args, arg_list)); CHECK(checked(realm_query_count(q2.get(), &count))); CHECK(count == 0); } @@ -1794,7 +1801,7 @@ TEST_CASE("C API", "[c_api]") { SECTION("realm_query_parse() errors") { // Invalid class key - CHECK(!realm_query_parse(realm, 123123123, "string == $0", 1, &arg)); + CHECK(!realm_query_parse(realm, 123123123, "string == $0", num_args, arg_list)); CHECK_ERR(RLM_ERR_NO_SUCH_TABLE); // Invalid syntax @@ -1808,7 +1815,6 @@ TEST_CASE("C API", "[c_api]") { SECTION("interpolate all types") { realm_value_t int_arg = rlm_int_val(123); - realm_value_t bool_arg = rlm_bool_val(true); realm_value_t string_arg = rlm_str_val("foobar"); static const uint8_t binary_data[3] = {1, 2, 3}; @@ -1820,21 +1826,42 @@ TEST_CASE("C API", "[c_api]") { realm_value_t object_id_arg = rlm_object_id_val("abc123abc123"); realm_value_t uuid_arg = rlm_uuid_val("01234567-9abc-4def-9012-3456789abcde"); realm_value_t link_arg = rlm_link_val(class_bar.key, realm_object_get_key(obj2.get())); - - auto q_int = cptr_checked(realm_query_parse(realm, class_foo.key, "int == $0", 1, &int_arg)); - auto q_bool = cptr_checked(realm_query_parse(realm, class_foo.key, "bool == $0", 1, &bool_arg)); - auto q_string = cptr_checked(realm_query_parse(realm, class_foo.key, "string == $0", 1, &string_arg)); - auto q_binary = cptr_checked(realm_query_parse(realm, class_foo.key, "binary == $0", 1, &binary_arg)); + realm_value_t list_arg[3] = {rlm_int_val(456), rlm_str_val("lol"), rlm_double_val(3.14)}; + + static const size_t num_args = 13; + realm_query_arg_t args[num_args] = { + realm_query_arg_t{1, false, &int_arg}, realm_query_arg_t{1, false, &bool_arg}, + realm_query_arg_t{1, false, &string_arg}, realm_query_arg_t{1, false, &binary_arg}, + realm_query_arg_t{1, false, ×tamp_arg}, realm_query_arg_t{1, false, &float_arg}, + realm_query_arg_t{1, false, &double_arg}, realm_query_arg_t{1, false, &decimal_arg}, + realm_query_arg_t{1, false, &object_id_arg}, realm_query_arg_t{1, false, &uuid_arg}, + realm_query_arg_t{1, false, &link_arg}, realm_query_arg_t{3, true, &list_arg[0]}, + realm_query_arg_t{0, true, nullptr}}; + realm_query_arg_t* arg_list = &args[0]; + + auto q_int = cptr_checked(realm_query_parse(realm, class_foo.key, "int == $0", num_args, arg_list)); + auto q_bool = cptr_checked(realm_query_parse(realm, class_foo.key, "bool == $1", num_args, arg_list)); + auto q_string = + cptr_checked(realm_query_parse(realm, class_foo.key, "string == $2", num_args, arg_list)); + auto q_binary = + cptr_checked(realm_query_parse(realm, class_foo.key, "binary == $3", num_args, arg_list)); auto q_timestamp = - cptr_checked(realm_query_parse(realm, class_foo.key, "timestamp == $0", 1, ×tamp_arg)); - auto q_float = cptr_checked(realm_query_parse(realm, class_foo.key, "float == $0", 1, &float_arg)); - auto q_double = cptr_checked(realm_query_parse(realm, class_foo.key, "double == $0", 1, &double_arg)); + cptr_checked(realm_query_parse(realm, class_foo.key, "timestamp == $4", num_args, arg_list)); + auto q_float = + cptr_checked(realm_query_parse(realm, class_foo.key, "float == $5", num_args, arg_list)); + auto q_double = + cptr_checked(realm_query_parse(realm, class_foo.key, "double == $6", num_args, arg_list)); auto q_decimal = - cptr_checked(realm_query_parse(realm, class_foo.key, "decimal == $0", 1, &decimal_arg)); + cptr_checked(realm_query_parse(realm, class_foo.key, "decimal == $7", num_args, arg_list)); auto q_object_id = - cptr_checked(realm_query_parse(realm, class_foo.key, "object_id == $0", 1, &object_id_arg)); - auto q_uuid = cptr_checked(realm_query_parse(realm, class_foo.key, "uuid == $0", 1, &uuid_arg)); - auto q_link = cptr_checked(realm_query_parse(realm, class_foo.key, "link == $0", 1, &link_arg)); + cptr_checked(realm_query_parse(realm, class_foo.key, "object_id == $8", num_args, arg_list)); + auto q_uuid = cptr_checked(realm_query_parse(realm, class_foo.key, "uuid == $9", num_args, arg_list)); + auto q_link = + cptr_checked(realm_query_parse(realm, class_foo.key, "link == $10", num_args, arg_list)); + auto q_list = + cptr_checked(realm_query_parse(realm, class_foo.key, "int == ANY $11", num_args, arg_list)); + auto q_empty_list = + cptr_checked(realm_query_parse(realm, class_foo.key, "int == ALL $12", num_args, arg_list)); CHECK(cptr_checked(realm_query_find_all(q_int.get()))); CHECK(cptr_checked(realm_query_find_all(q_bool.get()))); @@ -1847,29 +1874,31 @@ TEST_CASE("C API", "[c_api]") { CHECK(cptr_checked(realm_query_find_all(q_object_id.get()))); CHECK(cptr_checked(realm_query_find_all(q_uuid.get()))); CHECK(cptr_checked(realm_query_find_all(q_link.get()))); + CHECK(cptr_checked(realm_query_find_all(q_list.get()))); + CHECK(cptr_checked(realm_query_find_all(q_empty_list.get()))); SECTION("type mismatch") { - CHECK(!realm_query_parse(realm, class_foo.key, "int == $0", 1, &string_arg)); + CHECK(!realm_query_parse(realm, class_foo.key, "int == $2", num_args, arg_list)); CHECK_ERR(RLM_ERR_INVALID_QUERY); - CHECK(!realm_query_parse(realm, class_foo.key, "bool == $0", 1, &string_arg)); + CHECK(!realm_query_parse(realm, class_foo.key, "bool == $2", num_args, arg_list)); CHECK_ERR(RLM_ERR_INVALID_QUERY); - CHECK(!realm_query_parse(realm, class_foo.key, "string == $0", 1, &decimal_arg)); + CHECK(!realm_query_parse(realm, class_foo.key, "string == $7", num_args, arg_list)); CHECK_ERR(RLM_ERR_INVALID_QUERY); - CHECK(!realm_query_parse(realm, class_foo.key, "timestamp == $0", 1, &string_arg)); + CHECK(!realm_query_parse(realm, class_foo.key, "timestamp == $2", num_args, arg_list)); CHECK_ERR(RLM_ERR_INVALID_QUERY); - CHECK(!realm_query_parse(realm, class_foo.key, "double == $0", 1, &string_arg)); + CHECK(!realm_query_parse(realm, class_foo.key, "double == $2", num_args, arg_list)); CHECK_ERR(RLM_ERR_INVALID_QUERY); - CHECK(!realm_query_parse(realm, class_foo.key, "float == $0", 1, &string_arg)); + CHECK(!realm_query_parse(realm, class_foo.key, "float == $2", num_args, arg_list)); CHECK_ERR(RLM_ERR_INVALID_QUERY); - CHECK(!realm_query_parse(realm, class_foo.key, "binary == $0", 1, &int_arg)); + CHECK(!realm_query_parse(realm, class_foo.key, "binary == $0", num_args, arg_list)); CHECK_ERR(RLM_ERR_INVALID_QUERY); - CHECK(!realm_query_parse(realm, class_foo.key, "decimal == $0", 1, &string_arg)); + CHECK(!realm_query_parse(realm, class_foo.key, "decimal == $2", num_args, arg_list)); CHECK_ERR(RLM_ERR_INVALID_QUERY); - CHECK(!realm_query_parse(realm, class_foo.key, "object_id == $0", 1, &string_arg)); + CHECK(!realm_query_parse(realm, class_foo.key, "object_id == $2", num_args, arg_list)); CHECK_ERR(RLM_ERR_INVALID_QUERY); - CHECK(!realm_query_parse(realm, class_foo.key, "uuid == $0", 1, &string_arg)); + CHECK(!realm_query_parse(realm, class_foo.key, "uuid == $2", num_args, arg_list)); CHECK_ERR(RLM_ERR_INVALID_QUERY); - CHECK(!realm_query_parse(realm, class_foo.key, "link == $0", 1, &string_arg)); + CHECK(!realm_query_parse(realm, class_foo.key, "link == $2", num_args, arg_list)); CHECK_ERR(RLM_ERR_INVALID_QUERY); } } diff --git a/test/test_parser.cpp b/test/test_parser.cpp index f66e39c7c10..ca0e3b5eaa4 100644 --- a/test/test_parser.cpp +++ b/test/test_parser.cpp @@ -25,7 +25,6 @@ #include "test.hpp" - // Test independence and thread-safety // ----------------------------------- // @@ -218,6 +217,26 @@ static std::vector valid_queries = { "a == b and c==d sort(a ASC, b DESC) DISTINCT(p) sort(c ASC, d DESC) DISTINCT(q.r)", "a == b and c==d sort(a ASC, b DESC) DISTINCT( p ) sort( c ASC , d DESC ) DISTINCT(q.r , p) ", + // constant list comparisons + "a BETWEEN {1, 2}", + "a IN {1, 2, 3}", + "a IN {'one', 2, -3.0, null}", + "a IN {}", + "a == {1, 2}", + "a > {1, 2}", + "a >= {1, 2}", + "a < {1, 2}", + "a <= {1, 2}", + "a != {1, 2}", + "a ==[c] {'a'}", + "a beginswith {'a', 'b'}", + "a endswith {'a', 'b'}", + "a contains {'a', 'b'}", + "a like {'a*', 'b*'}", + "NOT a IN {1, 2}", + "ALL a IN {1, 2}", + "NONE a IN {1, 2}", + // limit "a=b LIMIT(1)", "a=b LIMIT ( 1 )", @@ -231,20 +250,6 @@ static std::vector valid_queries = { "a=b && c=d LIMIT(5) LIMIT(2)", "a=b LIMIT(5) SORT(age ASC) DISTINCT(name) LIMIT(2)", - /* - // include - "a=b INCLUDE(c)", - "a=b include(c,d)", - "a=b INCLUDE(c.d)", - "a=b INCLUDE(c.d.e, f.g, h)", - "a=b INCLUDE ( c )", - "a=b INCLUDE(d, e, f , g )", - "a=b INCLUDE(c) && d=f", - "a=b INCLUDE(c) INCLUDE(d)", - "a=b && c=d || e=f INCLUDE(g)", - "a=b LIMIT(5) SORT(age ASC) DISTINCT(name) INCLUDE(links1, links2)", - "a=b INCLUDE(links1, links2) LIMIT(5) SORT(age ASC) DISTINCT(name)", - */ // subquery expression "SUBQUERY(items, $x, $x.name == 'Tom').@size > 0", "SUBQUERY(items, $x, $x.name == 'Tom').@count > 0", @@ -280,11 +285,9 @@ static std::vector invalid_queries = { "0x = 1", "- = a", "a..b = a", - "{} = $0", // operators "0===>0", - "a between {}", "a between {1 2}", "0 contains1", "a contains_something", @@ -314,6 +317,14 @@ static std::vector invalid_queries = { "truepredicate &&", "truepredicate & truepredicate", + // constant list comparisons + "a IN {1, 2, 3", + "a IN 1, 2, 3}", + "a IN (1, 2, 3)", + "a IN [1, 2, 3]", + "a IN 1, 2, 3", + "a IN {b, c}", + // sort/distinct "SORT(p ASCENDING)", // no query conditions "a=b SORT(p)", // no asc/desc @@ -1472,11 +1483,11 @@ TEST(Parser_substitution) CHECK_EQUAL(message, "Request for argument at index 1 but only 1 argument is provided"); CHECK_THROW_ANY_GET_MESSAGE(verify_query_sub(test_context, t, "age > $2", args, /*num_args*/ 2, 0), message); CHECK_EQUAL(message, "Request for argument at index 2 but only 2 arguments are provided"); - CHECK_THROW_ANY_GET_MESSAGE(t->query("age > $0", std::vector{}), message); + CHECK_THROW_ANY_GET_MESSAGE(t->query("age > $0", std::vector{}, {}), message); CHECK_EQUAL(message, "Request for argument at index 0 but no arguments are provided"); - CHECK_THROW_ANY_GET_MESSAGE(t->query("age > $1", std::vector{{1}}), message); + CHECK_THROW_ANY_GET_MESSAGE(t->query("age > $1", std::vector{{1}}, {}), message); CHECK_EQUAL(message, "Request for argument at index 1 but only 1 argument is provided"); - CHECK_THROW_ANY_GET_MESSAGE(t->query("age > $2", std::vector{{1}, {2}}), message); + CHECK_THROW_ANY_GET_MESSAGE(t->query("age > $2", std::vector{{1}, {2}}, {}), message); CHECK_EQUAL(message, "Request for argument at index 2 but only 2 arguments are provided"); // Mixed types @@ -2497,6 +2508,18 @@ TEST(Parser_list_of_primitive_mixed) CHECK_EQUAL(mixed_list.sum(), Mixed(7.2)); CHECK_EQUAL(mixed_list.avg(), Mixed(2.4)); + /* + "table":[ + {"_key":0,"values":[]}, + {"_key":1,"values":[null]}, + {"_key":2,"values":[""]}, + {"_key":3,"values":[0,1,2]}, + {"_key":4,"values":[1,"2.20000000000000",3.3000000e+00,4.4000000000000004e+00]}, + {"_key":5,"values":["one","two","three","",null]}, + {"_key":6,"values":["foo",1,"1970-01-01 00:00:01","2.5",3.7000000e+00,"62e2905feb23128a10ab3973", + "00000000-0000-0000-0000-000000000000",null,false,true,null,null,null,"NaN"]} + ] + */ verify_query(test_context, t, "values.@count == 0", 1); verify_query(test_context, t, "values.@size == 1", 2); verify_query(test_context, t, "ANY values == NULL", 3); @@ -2524,6 +2547,10 @@ TEST(Parser_list_of_primitive_mixed) verify_query(test_context, t, "values.@max == 2", 1); verify_query(test_context, t, "values.@max == 4.4", 1); verify_query(test_context, t, "values.@max == uuid(00000000-0000-0000-0000-000000000000)", 1); + verify_query(test_context, t, "ANY values == {'one', 'two'}", 1); + verify_query(test_context, t, "values == {'one', 'two', 'three', '', null}", 1); + verify_query(test_context, t, "values == {}", 1); + verify_query(test_context, t, "NONE values == ANY {null, ''}", 3); } TEST(Parser_SortAndDistinctSerialisation) @@ -2594,9 +2621,9 @@ TEST(Parser_SortAndDistinctSerialisation) static TableView get_sorted_view(TableRef t, std::string query_string, query_parser::KeyPathMapping mapping = {}) { - Query q = t->query(query_string, {}, mapping); + Query q = t->query(query_string, std::vector{}, mapping); std::string query_description = q.get_description(mapping.get_backlink_class_prefix()); - Query q2 = t->query(query_description, {}, mapping); + Query q2 = t->query(query_description, std::vector{}, mapping); return q2.find_all(); } @@ -3697,10 +3724,6 @@ TEST_TYPES(Parser_AggregateShortcuts, std::true_type, std::false_type) verify_query(test_context, t, "NONE items.name == fav_item.name", 1); // only person 1 has items which are not their favourite - // ANY/SOME is not necessary but accepted - verify_query(test_context, t, "ANY fav_item.name == 'milk'", 1); - verify_query(test_context, t, "SOME fav_item.name == 'milk'", 1); - // multiple lists in path is supported verify_query(test_context, t, "ANY items.allergens.name == 'dairy'", 3); verify_query(test_context, t, "SOME items.allergens.name == 'dairy'", 3); @@ -3708,14 +3731,17 @@ TEST_TYPES(Parser_AggregateShortcuts, std::true_type, std::false_type) verify_query(test_context, t, "NONE items.allergens.name == 'dairy'", 0); std::string message; - // no list in path should throw + // the expression following ANY/SOME/ALL/NONE must be a keypath list + // currently this is restricted by the parser syntax so it is a predicate error + CHECK_THROW_ANY_GET_MESSAGE(verify_query(test_context, t, "ANY fav_item.name == 'milk'", 1), message); + CHECK_EQUAL(message, "The keypath following 'ANY' must contain a list"); + CHECK_THROW_ANY_GET_MESSAGE(verify_query(test_context, t, "SOME fav_item.name == 'milk'", 1), message); + CHECK_EQUAL(message, "The keypath following 'ANY' must contain a list"); CHECK_THROW_ANY_GET_MESSAGE(verify_query(test_context, t, "ALL fav_item.name == 'milk'", 1), message); CHECK_EQUAL(message, "The keypath following 'ALL' must contain a list"); CHECK_THROW_ANY_GET_MESSAGE(verify_query(test_context, t, "NONE fav_item.name == 'milk'", 1), message); CHECK_EQUAL(message, "The keypath following 'NONE' must contain a list"); - // the expression following ANY/SOME/ALL/NONE must be a keypath list - // currently this is restricted by the parser syntax so it is a predicate error CHECK_THROW_ANY(verify_query(test_context, t, "ANY 'milk' == fav_item.name", 1)); CHECK_THROW_ANY(verify_query(test_context, t, "SOME 'milk' == fav_item.name", 1)); CHECK_THROW_ANY(verify_query(test_context, t, "ALL 'milk' == fav_item.name", 1)); @@ -3800,6 +3826,61 @@ TEST(Parser_OperatorIN) } } + // RHS is a constant list + verify_query(test_context, t, "customer_id IN {0, 1, 2}", 3); + verify_query(test_context, t, "NOT customer_id IN {0}", 2); + verify_query(test_context, t, "customer_id != {0}", 2); + verify_query(test_context, t, "customer_id != {0, 1}", 3); + verify_query(test_context, t, "NOT customer_id IN {0, 1}", 1); + verify_query(test_context, t, "customer_id != {0, 1, 2}", 3); + verify_query(test_context, t, "customer_id > {0, 1}", 2); + verify_query(test_context, t, "customer_id > {4, 5, 6}", 0); + verify_query(test_context, t, "customer_id < {0, 1}", 1); + verify_query(test_context, t, "customer_id < {0}", 0); + verify_query(test_context, t, "customer_id >= {0, 1}", 3); + verify_query(test_context, t, "customer_id >= {2, 3, 4}", 1); + verify_query(test_context, t, "customer_id <= {0, 1}", 2); + verify_query(test_context, t, "customer_id <= {-1}", 0); + + verify_query(test_context, t, "fav_item.name IN {'milk', 'oranges', 'cereal'}", 2); + verify_query(test_context, t, "fav_item.price IN {6.5, 9.5}", 1); + verify_query(test_context, t, "fav_item.name IN {0, null, -1, 'not found', 3.14, oid(000000000000000000000000)}", + 0); + verify_query(test_context, t, "fav_item.name != {'milk', 'oranges'}", 3); + verify_query(test_context, t, "NOT fav_item.name IN {'milk', 'oranges'}", 1); + verify_query(test_context, t, "fav_item.name contains[c] {'ILK', 'Range'}", 2); + verify_query(test_context, t, "fav_item.name beginswith[c] {'MIL', 'CERe'}", 1); + verify_query(test_context, t, "fav_item.name endswith {'lk', 'EAL', 'GeS'}", 1); + verify_query(test_context, t, "fav_item.name endswith[c] {'lk', 'EAL', 'GeS'}", 2); + verify_query(test_context, t, "fav_item.name like {'*lk', '*zz*'}", 2); + + std::vector int_list = {0, 1, 2}; + std::vector strings_list = {"milk", "oranges", "cereal"}; + std::vector mixed_list = {"no match", + -1, + 3.14, + UUID(), + ObjectId::gen(), + Timestamp{0, 0}, + Mixed{}, + false, + 2.6f, + Decimal128(8.888), + ObjKey{}, + ObjLink{t->get_key(), people_keys[0]}}; + std::vector empty_list = {}; + util::Any args[] = {realm::null(), int_list, strings_list, mixed_list, empty_list}; + size_t num_args = 5; + verify_query_sub(test_context, t, "customer_id IN $1", args, num_args, 3); + verify_query_sub(test_context, t, "fav_item.name IN $2", args, num_args, 2); + verify_query_sub(test_context, t, "fav_item.name IN $3", args, num_args, 0); + verify_query_sub(test_context, t, "fav_item.name IN $4", args, num_args, 0); + verify_query_sub(test_context, t, "customer_id IN ANY $1", args, num_args, 3); + verify_query_sub(test_context, t, "customer_id IN ALL $1", args, num_args, 0); + verify_query_sub(test_context, t, "customer_id IN NONE $1", args, num_args, 0); + verify_query(test_context, t, "'dairy' in items.allergens.name", 3); + + // RHS is a list property verify_query(test_context, t, "5.5 IN items.price", 2); verify_query(test_context, t, "!(5.5 IN items.price)", 1); // group not verify_query(test_context, t, "'milk' IN items.name", 2); // string compare @@ -3809,21 +3890,198 @@ TEST(Parser_OperatorIN) verify_query(test_context, items, "20 IN @links.class_Person.items.account_balance", 1); // backlinks verify_query(test_context, t, "fav_item.price IN items.price", 2); // single property in list + // list property compared to a constant list + verify_query(test_context, t, "ANY {5.5, 4.0} IN ANY items.price", 2); + verify_query(test_context, t, "ALL {5.5, 4.0} IN items.price", 1); + verify_query(test_context, t, "ALL {5.5} IN items.price", 2); + verify_query(test_context, t, "NONE {5.5, 4.0} IN items.price", 1); + verify_query(test_context, t, "NONE {5.5, 4.0} IN ALL items.price", 2); + verify_query(test_context, t, "ANY {5.5, 4.0} IN ALL items.price", 1); + verify_query(test_context, t, "ANY {5.5, 4.0} IN NONE items.price", 2); + verify_query(test_context, t, "!(ANY {5.5, 4.0} IN ANY items.price)", 1); + verify_query(test_context, t, "ALL {5.5, 4.0} IN NONE items.price", 1); + verify_query(test_context, t, "ALL {5.5, 4.0, 9.5, 6.5} IN ALL items.price", 0); + verify_query(test_context, t, "ALL {5.5, 5.5} IN ALL items.price", 1); + verify_query(test_context, t, "ALL {6.0, 6.1, 1.1} <= ALL items.price", 1); + verify_query(test_context, t, "NONE {5.5, 4.0, 9.5, 6.5} IN ANY items.price", 0); + verify_query(test_context, t, "NONE {5.5, 4.0, 9.5, 6.5} IN ALL items.price", 2); + + verify_query(test_context, t, "ANY items.name contains[c] ANY {'A', 'B'}", 2); + verify_query(test_context, t, "ALL items.name contains[c] {'A', 'B'}", 1); + verify_query(test_context, t, "ALL items.name contains[c] NONE {'A', 'B'}", 1); // customer_id: 1 + verify_query(test_context, t, "NONE items.name contains[c] {'A', 'B'}", 1); + verify_query(test_context, t, "NONE items.name contains[c] ALL {'A', 'L'}", 1); // customer_id: 1 only "milk" + verify_query(test_context, t, "ANY items.name contains[c] NONE {'A', 'B'}", 2); // 0 and 1 both contains "milk" + verify_query(test_context, t, "ANY items.name contains[c] ALL {'A', 'B'}", 0); + // In the following we have a match if neither 5.5 or 4.0 cannot be found in item prices + verify_query(test_context, t, "NONE {5.5, 4.0} IN NONE items.price", 1); // customer_id: 0 + + // when neither side specifies ANY/ALL/NONE we do ordered list matching + verify_query(test_context, t, "{5.5, 4.0, 9.5, 6.5} == items.price", 1); + verify_query(test_context, t, "{5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5} == items.price", 1); + verify_query(test_context, t, "{9.5, 9.5, 6.5} == items.price", 1); + verify_query(test_context, t, "{5.5, 4.0} IN items.price", 0); + verify_query(test_context, t, "{} == items.price", 0); + verify_query(test_context, t, "!{} == items.price", 3); + verify_query(test_context, t, "{} != items.price", 3); + verify_query(test_context, t, "{5.5, 4.0, 9.5, 6.5} != items.price", 2); + verify_query(test_context, t, "{5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5} != items.price", 2); + verify_query(test_context, t, "{9.5, 9.5, 6.5} != items.price", 2); + verify_query(test_context, t, "{null} != items.price", 3); + verify_query(test_context, t, "items.price != items.price", 0); + verify_query(test_context, t, "{5.5, 9.5, 4.0, 6.5} == items.price", 0); + verify_query(test_context, t, "{9.5, 9.5, 6.5, 6.5} == items.price", 0); + verify_query(test_context, t, "items.name == {'milk', 'oranges', 'pizza', 'cereal'}", 1); + verify_query(test_context, t, "items.name == {'MILk', 'ORanges', 'piZZA', 'CeReAl'}", 0); + verify_query(test_context, t, "NOT items.name == {'milk', 'oranges', 'pizza', 'cereal'}", 2); + verify_query(test_context, t, "items.name ==[c] {'MILk', 'ORanges', 'piZZA', 'CeReAl'}", 1); + verify_query(test_context, t, "items.name contains[c] {'ilk', 'range', 'zza', 'cer'}", 1); + verify_query(test_context, t, "items.name != {'milk', 'oranges', 'pizza', 'cereal'}", 2); + verify_query(test_context, t, "items.name != {'asdf', 'sdf', 'asdf', 'asdf'}", 3); + verify_query(test_context, t, "items.name != {}", 3); + verify_query(test_context, t, "items.name != {'pizza', 'pizza', 'cereal'}", 2); + verify_query(test_context, t, "{5.6, 4.1, 9.6, 6.6} > items.price", 1); + + // empty constant list + verify_query(test_context, t, "{} IN items.price", 0); + verify_query(test_context, t, "{ } IN ALL items.price", 0); + verify_query(test_context, t, "{ } IN NONE items.price", 0); + verify_query(test_context, t, "ALL {} IN ALL items.price", 3); + verify_query(test_context, t, "ALL { } IN NONE items.price", 3); + verify_query(test_context, t, "ALL { } IN ANY items.price", 3); + verify_query(test_context, t, "NONE { } IN ALL items.price", 3); + verify_query(test_context, t, "NONE {} IN ANY items.price", 3); + + // one item in constant list + verify_query(test_context, t, "ANY {6.5} IN ANY items.price", 2); + verify_query(test_context, t, "{6.5} IN ANY items.price", 2); + verify_query(test_context, t, "{6.5} IN ALL items.price", 0); + verify_query(test_context, t, "{6.5} IN NONE items.price", 1); + verify_query(test_context, t, "ALL {6.5} IN ALL items.price", 0); + verify_query(test_context, t, "ALL {6.5} IN NONE items.price", 1); + verify_query(test_context, t, "ALL {6.5} IN ANY items.price", 2); + verify_query(test_context, t, "NONE {6.5} IN ALL items.price", 3); + verify_query(test_context, t, "NONE {6.5} IN ANY items.price", 1); + + // operators on a list + verify_query(test_context, t, "ALL {8, 10} / 2 >= ANY items.price", 1); + verify_query(test_context, t, "NONE {1, 2, 3} * 20 <= ANY items.price", 3); + verify_query(test_context, t, "ANY {1, 2, 3, 4, 5, 6} + 2 == ANY items.price", 1); + + // list property vs list property + verify_query(test_context, t, "items.price IN items.price", 3); + verify_query(test_context, t, "ALL items.price IN ALL items.price", 1); + verify_query(test_context, t, "ALL items.price IN ANY items.price", 3); + verify_query(test_context, t, "NONE items.price IN ANY items.price", 0); + verify_query(test_context, t, "items.price * 2 > items.price", 3); + verify_query(test_context, t, "ALL items.price * 2 > ALL items.price", 2); + verify_query(test_context, t, "ALL items.price * 2 > ANY items.price", 3); + verify_query(test_context, t, "NONE items.price * 2 > ANY items.price", 0); + + // unsupported combinations + CHECK_THROW(verify_query(test_context, t, "{1, 2, 3, 4, 5, 6} * {1, 2, 3} == items.price", 1), std::logic_error); + CHECK_THROW(verify_query(test_context, t, "{8, 10}.@size >= ANY items.price", 3), query_parser::SyntaxError); + CHECK_THROW(verify_query(test_context, t, "{8, 10}.@max >= ANY items.price", 3), query_parser::SyntaxError); + CHECK_THROW(verify_query(test_context, t, "{8, 10}.@min >= ANY items.price", 3), query_parser::SyntaxError); + CHECK_THROW(verify_query(test_context, t, "{8, 10}.@avg >= ANY items.price", 3), query_parser::SyntaxError); + CHECK_THROW(verify_query(test_context, t, "{8, 10}.length >= ANY items.price", 3), query_parser::SyntaxError); + // aggregate modifiers must operate on a list CHECK_THROW(verify_query(test_context, t, "ANY 5.5 IN items.price", 2), query_parser::SyntaxError); CHECK_THROW(verify_query(test_context, t, "SOME 5.5 IN items.price", 2), query_parser::SyntaxError); CHECK_THROW(verify_query(test_context, t, "ALL 5.5 IN items.price", 1), query_parser::SyntaxError); CHECK_THROW(verify_query(test_context, t, "NONE 5.5 IN items.price", 1), query_parser::SyntaxError); + CHECK_THROW(verify_query(test_context, t, "ALL customer_id IN {0, 1, 2}", 3), query_parser::InvalidQueryError); + CHECK_THROW(verify_query(test_context, t, "NONE customer_id IN {0, 1, 2}", 3), query_parser::InvalidQueryError); + CHECK_THROW(verify_query_sub(test_context, t, "customer_id IN NONE $0", args, num_args, 0), + query_parser::InvalidQueryError); + CHECK_THROW(verify_query_sub(test_context, t, "customer_id IN ANY $0", args, num_args, 0), + query_parser::InvalidQueryError); + CHECK_THROW(verify_query_sub(test_context, t, "customer_id IN ALL $0", args, num_args, 0), + query_parser::InvalidQueryError); CHECK_THROW_EX(verify_query(test_context, t, "items.price IN 5.5", 1), query_parser::InvalidQueryArgError, - CHECK_EQUAL(e.what(), "The keypath following 'IN' must contain a list")); + CHECK_EQUAL(e.what(), "The keypath following 'IN' must contain a list. Found '5.5'")); CHECK_THROW_EX(verify_query(test_context, t, "5.5 in fav_item.price", 1), query_parser::InvalidQueryArgError, - CHECK_EQUAL(e.what(), "The keypath following 'IN' must contain a list")); - verify_query(test_context, t, "'dairy' in items.allergens.name", 3); - // list property vs list property is not supported by core yet - CHECK_THROW_EX( - verify_query(test_context, t, "items.price IN items.price", 0), query_parser::InvalidQueryError, - CHECK_EQUAL(e.what(), "Comparison between two lists is not supported ('items.price' and 'items.price')")); + CHECK_EQUAL(e.what(), "The keypath following 'IN' must contain a list. Found 'fav_item.price'")); +} + +TEST(Parser_ListVsList) +{ + Group g; + TableRef table = g.add_table_with_primary_key("table", type_Int, "id"); + auto col = table->add_column_list(type_Int, "integers"); + auto list = table->create_object_with_primary_key(1).get_list(col); + list.add(1); + list.add(2); + list = table->create_object_with_primary_key(2).get_list(col); + list.add(11); + list.add(5); + list.add(9); + list.add(5); + + // None of {1, 2, 3} matches all of integers + verify_query(test_context, table, "ANY {1, 2, 3} == ALL integers", 0); + verify_query(test_context, table, "ALL integers == ANY {1, 2, 3}", 1); + verify_query(test_context, table, "{7, 3, 8, 0} < integers", 1); + + TableView tv; + // ANY + tv = table->query("ANY {1, 2, 3} == ALL integers").find_all(); + CHECK_EQUAL(tv.size(), 0); + tv = table->query("ANY {1, 2, 3} > ALL integers").find_all(); + if (CHECK_EQUAL(tv.size(), 1)) + CHECK_EQUAL(tv.get_object(0).get_primary_key().get_int(), 1); + + tv = table->query("ANY {4, 8} == ANY integers").find_all(); + CHECK_EQUAL(tv.size(), 0); + tv = table->query("ANY {1, 2, 3} == ANY integers").find_all(); + if (CHECK_EQUAL(tv.size(), 1)) + CHECK_EQUAL(tv.get_object(0).get_primary_key().get_int(), 1); + + tv = table->query("ANY {1, 2, 3} == NONE integers").find_all(); + CHECK_EQUAL(tv.size(), 2); + tv = table->query("ANY {1, 2, 7} <= NONE integers").find_all(); + if (CHECK_EQUAL(tv.size(), 1)) + CHECK_EQUAL(tv.get_object(0).get_primary_key().get_int(), 1); + tv = table->query("ANY {-1, 0} < NONE integers").find_all(); + CHECK_EQUAL(tv.size(), 0); + + // ALL + tv = table->query("ALL {1, 2, 3} > ALL integers").find_all(); + CHECK_EQUAL(tv.size(), 0); + tv = table->query("ALL {4, 8} > ALL integers").find_all(); + if (CHECK_EQUAL(tv.size(), 1)) + CHECK_EQUAL(tv.get_object(0).get_primary_key().get_int(), 1); + tv = table->query("ALL {1, 2, 12} < ANY integers").find_all(); + CHECK_EQUAL(tv.size(), 0); + tv = table->query("ALL {4, 8} > ANY integers").find_all(); + if (CHECK_EQUAL(tv.size(), 1)) + CHECK_EQUAL(tv.get_object(0).get_primary_key().get_int(), 1); + tv = table->query("ALL {2, 5} == NONE integers").find_all(); + CHECK_EQUAL(tv.size(), 0); + tv = table->query("ALL {3, 1, 4, 3} == NONE integers").find_all(); + if (CHECK_EQUAL(tv.size(), 1)) + CHECK_EQUAL(tv.get_object(0).get_primary_key().get_int(), 2); + + // NONE + tv = table->query("NONE {1, 2, 3, 12} > ALL integers").find_all(); + CHECK_EQUAL(tv.size(), 0); + tv = table->query("NONE {4, 8} > ALL integers").find_all(); + if (CHECK_EQUAL(tv.size(), 1)) + CHECK_EQUAL(tv.get_object(0).get_primary_key().get_int(), 2); + tv = table->query("NONE {1, 12} > ANY integers").find_all(); + CHECK_EQUAL(tv.size(), 0); + tv = table->query("NONE {4, 8} < ANY integers").find_all(); + if (CHECK_EQUAL(tv.size(), 1)) + CHECK_EQUAL(tv.get_object(0).get_primary_key().get_int(), 1); + tv = table->query("NONE {-1, 12} < NONE integers").find_all(); + CHECK_EQUAL(tv.size(), 0); + tv = table->query("NONE {-1, 5} < NONE integers").find_all(); + if (CHECK_EQUAL(tv.size(), 1)) + CHECK_EQUAL(tv.get_object(0).get_primary_key().get_int(), 2); + tv = table->query("NONE {0, 1} < NONE integers").find_all(); + CHECK_EQUAL(tv.size(), 2); } TEST(Parser_Object) @@ -4511,8 +4769,7 @@ TEST(Parser_TypeOfValue) origin->size()); verify_query(test_context, origin, "links.mixed.@type == 'numeric' || links.mixed.@type == 'string'", origin->size()); - // TODO: enable this when IN is supported for list constants - // verify_query(test_context, origin, "links.mixed.@type IN {'numeric', 'string'}", origin->size()); + verify_query(test_context, origin, "ANY links.mixed.@type IN ANY {'numeric', 'string'}", origin->size()); verify_query(test_context, table, "mixed.@type == int.@type", table->size() - nb_strings - 5); verify_query(test_context, origin, "link.@type == link.mixed.@type", 0); @@ -4542,6 +4799,10 @@ TEST(Parser_TypeOfValue) CHECK_THROW_EX( verify_query(test_context, table, "mixed.@type == 'asdf'", 1), query_parser::InvalidQueryArgError, CHECK(std::string(e.what()).find("Unable to parse the type attribute string 'asdf'") != std::string::npos)); + CHECK_THROW_EX( + verify_query(test_context, origin, "links.mixed.@type IN {'numeric', 'asdf'}", 0), + query_parser::InvalidQueryArgError, + CHECK(std::string(e.what()).find("Unable to parse the type attribute string 'asdf'") != std::string::npos)); CHECK_THROW_EX( verify_query(test_context, table, "mixed.@type == ''", 1), query_parser::InvalidQueryArgError, CHECK(std::string(e.what()).find("Unable to parse the type attribute string ''") != std::string::npos)); @@ -4641,6 +4902,11 @@ TEST(Parser_Dictionary) verify_query(test_context, foo, "dict['Value'] > 50", expected); verify_query(test_context, foo, "ANY dict.@keys == 'Foo'", 20); verify_query(test_context, foo, "NONE dict.@keys == 'Value'", 23); + verify_query(test_context, foo, "dict.@keys == {'Bar'}", 20); + verify_query(test_context, foo, "ANY dict.@keys == {'Bar'}", 100); + verify_query(test_context, foo, "dict.@keys == {'Bar', 'Foo'}", 3); + verify_query(test_context, foo, "dict['Value'] == {}", 0); + verify_query(test_context, foo, "dict['Value'] == {0, 100}", 3); verify_query(test_context, foo, "dict['Value'].@type == 'int'", num_ints_for_value); verify_query(test_context, foo, "dict.@type == 'int'", 100); // ANY is implied, all have int values verify_query(test_context, foo, "ALL dict.@type == 'int'", 100); // all dictionaries have ints @@ -4952,6 +5218,12 @@ TEST(Parser_SetMixed) verify_query(test_context, table, "ALL set < value && set.@size > 0", 0); verify_query(test_context, table, "ALL set == value", 2); // 2, 3 verify_query(test_context, table, "NONE set == value", 3); // 1, 3, 5 + verify_query(test_context, table, "set == {}", 1); + verify_query(test_context, table, "ANY set == {300}", 2); + verify_query(test_context, table, "ALL set == ALL {300} && set.@size > 0", 1); + verify_query(test_context, table, "set == {300}", 1); + verify_query(test_context, table, "set == {300, 3, 'hello'}", 0); // order not matching + verify_query(test_context, table, "set == {3, 300, 'hello'}", 1); // order matching verify_query(test_context, table, "set == NULL", 2); verify_query(test_context, table, "set beginswith[c] 'HE'", 1); verify_query(test_context, table, "set endswith[c] 'D'", 1); diff --git a/test/test_query.cpp b/test/test_query.cpp index c68339cac29..b8405e0a87d 100644 --- a/test/test_query.cpp +++ b/test/test_query.cpp @@ -150,7 +150,7 @@ TEST(Query_Parser) books.create_object().set_all("Biophysics: Searching for Principles", "William Bialek", 640); // Typed table: - Query q = books.query("pages >= $0 && author == $1", {{200, "David Griffiths"}}); + Query q = books.query("pages >= $0 && author == $1", std::vector{200, "David Griffiths"}); auto match = q.find(); CHECK_EQUAL(obj2.get_key(), match); // You don't need to create a query object first: