diff --git a/src/Storages/MergeTree/KeyCondition.cpp b/src/Storages/MergeTree/KeyCondition.cpp index 3bea261fd52..a4ac9bf8c25 100644 --- a/src/Storages/MergeTree/KeyCondition.cpp +++ b/src/Storages/MergeTree/KeyCondition.cpp @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -55,11 +56,15 @@ String Range::toString() const } -/// Example: for `Hello\_World% ...` string it returns `Hello_World`, and for `%test%` returns an empty string. -/// If perfect_prefix_match == true, only consider pattern in the format `prefix%_` -String extractFixedPrefixFromLikePattern(const String & like_pattern, bool perfect_prefix_match) +/// Returns the prefix of like_pattern before the first wildcard, e.g. 'Hello\_World% ...' --> 'Hello\_World' +/// We call a prefix "perfect" if: +/// - (1) the prefix must have willcard +/// - (2) the first wildcard is '%' and is only followed by nothing or other '%' +/// e.g. 'test%' or 'test%% has perfect prefix 'test', 'test%x', 'test%_' or 'test_' has no perfect prefix. +String extractFixedPrefixFromLikePattern(std::string_view like_pattern, bool require_perfect_prefix) { String fixed_prefix; + fixed_prefix.reserve(like_pattern.size()); const char * pos = like_pattern.data(); const char * end = pos + like_pattern.size(); @@ -68,12 +73,13 @@ String extractFixedPrefixFromLikePattern(const String & like_pattern, bool perfe switch (*pos) { case '%': - [[fallthrough]]; case '_': - if (perfect_prefix_match && std::find_if(pos+1, end, [](const char c) { return c != '%' && c != '_'; }) != end) - return ""; + if (require_perfect_prefix) + { + bool is_prefect_prefix = std::all_of(pos, end, [](auto c) { return c == '%'; }); + return is_prefect_prefix ? fixed_prefix : ""; + } return fixed_prefix; - case '\\': ++pos; if (pos == end) @@ -81,12 +87,13 @@ String extractFixedPrefixFromLikePattern(const String & like_pattern, bool perfe [[fallthrough]]; default: fixed_prefix += *pos; - break; } ++pos; } - + /// If we can reach this code, it means there was no wildcard found in the pattern, so it is not a perfect prefix + if (require_perfect_prefix) + return ""; return fixed_prefix; } @@ -349,7 +356,7 @@ const KeyCondition::AtomMap KeyCondition::atom_map if (value.getType() != Field::Types::String) return false; - String prefix = extractFixedPrefixFromLikePattern(value.get()); + String prefix = extractFixedPrefixFromLikePattern(value.get(), /*required_perfect_prefix*/ false); if (prefix.empty()) return false; @@ -370,7 +377,7 @@ const KeyCondition::AtomMap KeyCondition::atom_map if (value.getType() != Field::Types::String) return false; - String prefix = extractFixedPrefixFromLikePattern(value.get(), true); + String prefix = extractFixedPrefixFromLikePattern(value.get(), /*required_perfect_prefix*/ true); if (prefix.empty()) return false; diff --git a/src/Storages/MergeTree/KeyCondition.h b/src/Storages/MergeTree/KeyCondition.h index e7eb855b1cc..81cfea3d359 100644 --- a/src/Storages/MergeTree/KeyCondition.h +++ b/src/Storages/MergeTree/KeyCondition.h @@ -485,6 +485,6 @@ private: bool strict; }; -String extractFixedPrefixFromLikePattern(const String & like_pattern, bool perfect_prefix_match = false); +String extractFixedPrefixFromLikePattern(std::string_view like_pattern, bool require_perfect_prefix); } diff --git a/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp b/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp index 275365648f3..776b85cbffd 100644 --- a/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp +++ b/src/Storages/System/StorageSystemMergeTreeMetadataCache.cpp @@ -116,7 +116,7 @@ void StorageSystemMergeTreeMetadataCache::fillData(MutableColumns & res_columns, } else { - String target = extractFixedPrefixFromLikePattern(key); + String target = extractFixedPrefixFromLikePattern(key, /*required_perfect_prefix*/ false); if (target.empty()) throw Exception( "SELECT from system.merge_tree_metadata_cache table must contain condition like key = 'key' or key LIKE 'prefix%' in WHERE clause.", ErrorCodes::BAD_ARGUMENTS); diff --git a/tests/queries/0_stateless/02458_key_condition_not_like_prefix.reference b/tests/queries/0_stateless/02458_key_condition_not_like_prefix.reference index 87766d889a3..02357561e02 100644 --- a/tests/queries/0_stateless/02458_key_condition_not_like_prefix.reference +++ b/tests/queries/0_stateless/02458_key_condition_not_like_prefix.reference @@ -1 +1,2 @@ 200000 +200000 diff --git a/tests/queries/0_stateless/02458_key_condition_not_like_prefix.sql b/tests/queries/0_stateless/02458_key_condition_not_like_prefix.sql index 2c1402df27e..e821b16ed5c 100644 --- a/tests/queries/0_stateless/02458_key_condition_not_like_prefix.sql +++ b/tests/queries/0_stateless/02458_key_condition_not_like_prefix.sql @@ -2,4 +2,11 @@ CREATE TABLE data (str String) ENGINE=MergeTree ORDER BY str; INSERT INTO data (str) SELECT 'aa' FROM numbers(100000); INSERT INTO data (str) SELECT 'ba' FROM numbers(100000); INSERT INTO data (str) SELECT 'ca' FROM numbers(100000); -SELECT count()FROM data WHERE str NOT LIKE 'a%' SETTINGS force_primary_key=1; +SELECT count() FROM data WHERE str NOT LIKE 'a%' SETTINGS force_primary_key=1; +SELECT count() FROM data WHERE str NOT LIKE 'a%%' SETTINGS force_primary_key=1; +SELECT count() FROM data WHERE str NOT LIKE 'a' SETTINGS force_primary_key=1; -- { serverError 277 } +SELECT count() FROM data WHERE str NOT LIKE '%a' SETTINGS force_primary_key=1; -- { serverError 277 } +SELECT count() FROM data WHERE str NOT LIKE 'a_' SETTINGS force_primary_key=1; -- { serverError 277 } +SELECT count() FROM data WHERE str NOT LIKE 'a%_' SETTINGS force_primary_key=1; -- { serverError 277 } +SELECT count() FROM data WHERE str NOT LIKE '_a' SETTINGS force_primary_key=1; -- { serverError 277 } +SELECT count() FROM data WHERE str NOT LIKE 'a%\_' SETTINGS force_primary_key=1; -- { serverError 277 }