diff --git a/docs/en/engines/table-engines/integrations/mongodb.md b/docs/en/engines/table-engines/integrations/mongodb.md index 633027c7064..f466ee4eb47 100644 --- a/docs/en/engines/table-engines/integrations/mongodb.md +++ b/docs/en/engines/table-engines/integrations/mongodb.md @@ -55,6 +55,18 @@ Only constant literals are allowed. PREWHERE and HAVING are not supported. +#### Note: +It's always better to explicitly set type of literal because Mongo requires strict typed filters.\ +For example you want to filter by `Date`: +```sql +SELECT * FROM mongo_table WHERE date = '2024-01-01' +``` +This will not work because Mongo will not cast string to `Date`, so you need to cast it manually: +```sql +SELECT * FROM mongo_table WHERE date = '2024-01-01'::Date OR date = toDate('2024-01-01') +``` +This applied for `Date`, `Date32`, `DateTime`, `Bool`, `UUID`. + ### LIMIT and OFFSET Only `LIMIT` is supported. diff --git a/src/Common/BSONCXXHelper.h b/src/Common/BSONCXXHelper.h index 233af2692b2..fd8223ae98b 100644 --- a/src/Common/BSONCXXHelper.h +++ b/src/Common/BSONCXXHelper.h @@ -28,40 +28,38 @@ static bsoncxx::types::bson_value::value fieldAsBSONValue(const Field & field, c switch (type->getTypeId()) { case TypeIndex::String: - return field.safeGet(); + return bsoncxx::types::b_string{field.safeGet()}; case TypeIndex::UInt8: { if (isBool(type)) - return field.safeGet() != 0; - return static_cast(field.safeGet()); + return bsoncxx::types::b_bool{field.safeGet() != 0}; + return bsoncxx::types::b_int32{static_cast(field.safeGet())}; } case TypeIndex::UInt16: - return static_cast(field.safeGet()); + return bsoncxx::types::b_int32{static_cast(field.safeGet())}; case TypeIndex::UInt32: - return static_cast(field.safeGet()); + return bsoncxx::types::b_int64{static_cast(field.safeGet())}; case TypeIndex::UInt64: - return static_cast(field.safeGet()); + return bsoncxx::types::b_double{static_cast(field.safeGet())}; case TypeIndex::Int8: - return field.safeGet(); + return bsoncxx::types::b_int32{static_cast(field.safeGet())}; case TypeIndex::Int16: - return field.safeGet(); + return bsoncxx::types::b_int32{static_cast(field.safeGet())}; case TypeIndex::Int32: - return field.safeGet(); + return bsoncxx::types::b_int32{static_cast(field.safeGet())}; case TypeIndex::Int64: - return field.safeGet(); + return bsoncxx::types::b_int64{field.safeGet()}; case TypeIndex::Float32: - return field.safeGet(); + return bsoncxx::types::b_double{field.safeGet()}; case TypeIndex::Float64: - return field.safeGet(); + return bsoncxx::types::b_double{field.safeGet()}; case TypeIndex::Date: - return std::chrono::milliseconds(field.safeGet() * 1000); + return bsoncxx::types::b_date{std::chrono::seconds{field.safeGet() * 86400}}; case TypeIndex::Date32: - return std::chrono::milliseconds(field.safeGet() * 1000); + return bsoncxx::types::b_date{std::chrono::seconds{field.safeGet() * 86400}}; case TypeIndex::DateTime: - return std::chrono::milliseconds(field.safeGet() * 1000); - case TypeIndex::DateTime64: - return std::chrono::milliseconds(field.safeGet().getValue()); + return bsoncxx::types::b_date{std::chrono::seconds{field.safeGet()}}; case TypeIndex::UUID: - return static_cast(formatUUID(field.safeGet())); + return bsoncxx::types::b_string{static_cast(formatUUID(field.safeGet()))}; case TypeIndex::Tuple: { auto arr = array(); for (const auto & elem : field.safeGet()) diff --git a/src/Storages/StorageMongoDB.cpp b/src/Storages/StorageMongoDB.cpp index c9b4278f766..873b84665c8 100644 --- a/src/Storages/StorageMongoDB.cpp +++ b/src/Storages/StorageMongoDB.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -178,61 +179,79 @@ std::string mongoFuncName(const std::string & func) throw Exception(ErrorCodes::FAILED_TO_BUILD_MONGODB_QUERY, "Function '{}' is not supported. You can disable this error with 'SET mongodb_fail_on_query_build_error=0', but this may cause poor performance, and is highly not recommended.", func); } -bsoncxx::document::value StorageMongoDB::visitWhereFunction(const ContextPtr & context, const FunctionNode * func) +std::optional StorageMongoDB::visitWhereFunction(const ContextPtr & context, const FunctionNode * func) { - if (!func->getArguments().getNodes().empty()) + if (func->getArguments().getNodes().empty()) + return {}; + + std::cout << func->dumpTree() << std::endl; + if (const auto & column = func->getArguments().getNodes().at(0)->as()) { - if (const auto & column = func->getArguments().getNodes().at(0)->as()) + std::cout << column->dumpTree() << std::endl; + // Skip unknows columns, which don't belong to the table. + const auto & table = column->getColumnSource()->as(); + if (!table) + return {}; + + std::cout << table->getStorage()->getStorageID().getFullTableName() << std::endl; + std::cout << this->getStorageID().getFullTableName() << std::endl; + // Skip columns from other tables in JOIN queries. + if (table->getStorage()->getStorageID().getFullTableName() != this->getStorageID().getFullTableName()) + return {}; + + // Only these function can have exactly one argument and be passed to MongoDB. + if (func->getFunctionName() == "isNull") + return make_document(kvp(column->getColumnName(), make_document(kvp("$eq", bsoncxx::types::b_null{})))); + if (func->getFunctionName() == "isNotNull") + return make_document(kvp(column->getColumnName(), make_document(kvp("$ne", bsoncxx::types::b_null{})))); + if (func->getFunctionName() == "empty") + return make_document(kvp(column->getColumnName(), make_document(kvp("$in", make_array(bsoncxx::types::b_null{}, ""))))); + if (func->getFunctionName() == "notEmpty") + return make_document(kvp(column->getColumnName(), make_document(kvp("$nin", make_array(bsoncxx::types::b_null{}, ""))))); + + auto func_name = mongoFuncName(func->getFunctionName()); + if (func->getArguments().getNodes().size() == 2) { - if (func->getFunctionName() == "isNull") - return make_document(kvp(column->getColumnName(), make_document(kvp("$eq", bsoncxx::types::b_null{})))); - if (func->getFunctionName() == "isNotNull") - return make_document(kvp(column->getColumnName(), make_document(kvp("$ne", bsoncxx::types::b_null{})))); - if (func->getFunctionName() == "empty") - return make_document(kvp(column->getColumnName(), make_document(kvp("$in", make_array(bsoncxx::types::b_null{}, ""))))); - if (func->getFunctionName() == "notEmpty") - return make_document(kvp(column->getColumnName(), make_document(kvp("$nin", make_array(bsoncxx::types::b_null{}, ""))))); + const auto & value = func->getArguments().getNodes().at(1); - if (func->getArguments().getNodes().size() == 2) + if (const auto & const_value = value->as()) { - auto func_name = mongoFuncName(func->getFunctionName()); + std::optional func_value{}; + if (column->getColumnName() == "_id") + func_value = fieldAsOID(const_value->getValue()); + else + func_value = fieldAsBSONValue(const_value->getValue(), const_value->getResultType()); - const auto & value = func->getArguments().getNodes().at(1); - if (const auto & const_value = value->as()) - { - std::optional func_value{}; - if (column->getColumnName() == "_id") - func_value = fieldAsOID(const_value->getValue()); - else - func_value = fieldAsBSONValue(const_value->getValue(), const_value->getResultType()); + if (func_name == "$in" && func_value->view().type() != bsoncxx::v_noabi::type::k_array) + func_name = "$eq"; + if (func_name == "$nin" && func_value->view().type() != bsoncxx::v_noabi::type::k_array) + func_name = "$ne"; - if (func_name == "$in" && func_value->view().type() != bsoncxx::v_noabi::type::k_array) - func_name = "$eq"; - if (func_name == "$nin" && func_value->view().type() != bsoncxx::v_noabi::type::k_array) - func_name = "$ne"; - - return make_document(kvp(column->getColumnName(), make_document(kvp(func_name, std::move(*func_value))))); - } - - if (const auto & func_value = value->as()) - return make_document( - kvp(column->getColumnName(), make_document(kvp(func_name, visitWhereFunction(context, func_value))))); + return make_document(kvp(column->getColumnName(), make_document(kvp(func_name, std::move(*func_value))))); } - } - else - { - auto arr = bsoncxx::builder::basic::array{}; - for (const auto & elem : func->getArguments().getNodes()) - { - if (const auto & elem_func = elem->as()) - arr.append(visitWhereFunction(context, elem_func)); - } - if (!arr.view().empty()) - return make_document(kvp(mongoFuncName(func->getFunctionName()), arr)); + + if (const auto & func_value = value->as()) + if (const auto & res_value = visitWhereFunction(context, func_value); res_value.has_value()) + return make_document(kvp(column->getColumnName(), make_document(kvp(func_name, *res_value)))); } } + else + { + auto arr = bsoncxx::builder::basic::array{}; + for (const auto & elem : func->getArguments().getNodes()) + { + if (const auto & elem_func = elem->as()) + if (const auto & res_value = visitWhereFunction(context, elem_func); res_value.has_value()) + arr.append(*res_value); + } + if (!arr.view().empty()) + return make_document(kvp(mongoFuncName(func->getFunctionName()), arr)); + } - throw Exception(ErrorCodes::FAILED_TO_BUILD_MONGODB_QUERY, "Only constant expressions are supported in WHERE section. You can disable this error with 'SET mongodb_fail_on_query_build_error=0', but this may cause poor performance, and is highly not recommended."); + throw Exception( + ErrorCodes::FAILED_TO_BUILD_MONGODB_QUERY, + "Only constant expressions are supported in WHERE section. You can disable this error with 'SET " + "mongodb_fail_on_query_build_error=0', but this may cause poor performance, and is highly not recommended."); } bsoncxx::document::value StorageMongoDB::buildMongoDBQuery(const ContextPtr & context, mongocxx::options::find & options, const SelectQueryInfo & query, const Block & sample_block) @@ -326,11 +345,11 @@ bsoncxx::document::value StorageMongoDB::buildMongoDBQuery(const ContextPtr & co } } - if (!filter.has_value()) - throw Exception(ErrorCodes::FAILED_TO_BUILD_MONGODB_QUERY, "Only constant expressions are supported in WHERE section. You can disable this error with 'SET mongodb_fail_on_query_build_error=0', but this may cause poor performance, and is highly not recommended."); - - LOG_DEBUG(log, "MongoDB query has built: '{}'.", bsoncxx::to_json(*filter)); - return std::move(*filter); + if (filter.has_value()) + { + LOG_DEBUG(log, "MongoDB query has built: '{}'.", bsoncxx::to_json(*filter)); + return std::move(*filter); + } } catch (Exception & e) { diff --git a/src/Storages/StorageMongoDB.h b/src/Storages/StorageMongoDB.h index 68c24229f77..5133af08ed7 100644 --- a/src/Storages/StorageMongoDB.h +++ b/src/Storages/StorageMongoDB.h @@ -60,7 +60,7 @@ public: size_t num_streams) override; private: - static bsoncxx::document::value visitWhereFunction(const ContextPtr & context, const FunctionNode * func); + std::optional visitWhereFunction(const ContextPtr & context, const FunctionNode * func); bsoncxx::document::value buildMongoDBQuery(const ContextPtr & context, mongocxx::options::find & options, const SelectQueryInfo & query, const Block & sample_block); const MongoDBConfiguration configuration;