Skip to content

Commit 0c37fe9

Browse files
Merge pull request #12179 from azat/GROUP-BY-injective-elimination-dictGet-fixes
Fix dictGet arguments check during GROUP BY injective functions elimination
2 parents cbb539c + 6a04de6 commit 0c37fe9

7 files changed

+53
-3
lines changed

src/Functions/FunctionsExternalDictionaries.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ class FunctionDictHelper
9999

100100
bool isDictGetFunctionInjective(const Block & sample_block)
101101
{
102+
/// Assume non-injective by default
103+
if (!sample_block)
104+
return false;
105+
102106
if (sample_block.columns() != 3 && sample_block.columns() != 4)
103107
throw Exception{"Function dictGet... takes 3 or 4 arguments", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH};
104108

src/Functions/IFunction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ class IFunctionBase
133133
* But we assume, that it is injective. This could be documented as implementation-specific behaviour.
134134
*
135135
* sample_block should contain data types of arguments and values of constants, if relevant.
136+
* NOTE: to check is function injective with any arguments, you can pass
137+
* empty block as sample_block (since most of the time function will
138+
* ignore it anyway, and creating arguments just for checking is
139+
* function injective or not is overkill).
136140
*/
137141
virtual bool isInjective(const Block & /*sample_block*/) const { return false; }
138142

src/Interpreters/SyntaxAnalyzer.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ void executeScalarSubqueries(ASTPtr & query, const Context & context, size_t sub
248248

249249
const std::unordered_set<String> possibly_injective_function_names
250250
{
251+
"dictGet",
251252
"dictGetString",
252253
"dictGetUInt8",
253254
"dictGetUInt16",
@@ -327,10 +328,18 @@ void optimizeGroupBy(ASTSelectQuery * select_query, const NameSet & source_colum
327328
continue;
328329
}
329330

330-
const auto & dict_name = function->arguments->children[0]->as<ASTLiteral &>().value.safeGet<String>();
331-
const auto & dict_ptr = context.getExternalDictionariesLoader().getDictionary(dict_name);
332-
const auto & attr_name = function->arguments->children[1]->as<ASTLiteral &>().value.safeGet<String>();
331+
const auto * dict_name_ast = function->arguments->children[0]->as<ASTLiteral>();
332+
const auto * attr_name_ast = function->arguments->children[1]->as<ASTLiteral>();
333+
if (!dict_name_ast || !attr_name_ast)
334+
{
335+
++i;
336+
continue;
337+
}
338+
339+
const auto & dict_name = dict_name_ast->value.safeGet<String>();
340+
const auto & attr_name = attr_name_ast->value.safeGet<String>();
333341

342+
const auto & dict_ptr = context.getExternalDictionariesLoader().getDictionary(dict_name);
334343
if (!dict_ptr->isInjective(attr_name))
335344
{
336345
++i;

tests/queries/0_stateless/01375_GROUP_BY_injective_elimination_dictGet_BAD_ARGUMENTS.reference

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SELECT dictGetString(concat('default', '.countryId'), 'country', toUInt64(number)) AS country FROM numbers(2) GROUP BY country; -- { serverError 36; }
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1.1
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
-- https://github.com/ClickHouse/ClickHouse/issues/11469
2+
SELECT dictGet('default.countryId', 'country', toUInt64(number)) AS country FROM numbers(2) GROUP BY country; -- { serverError 36; }
3+
4+
5+
-- with real dictionary
6+
DROP TABLE IF EXISTS dictdb_01376.table_for_dict;
7+
DROP DICTIONARY IF EXISTS dictdb_01376.dict_exists;
8+
DROP DATABASE IF EXISTS dictdb_01376;
9+
10+
CREATE DATABASE dictdb_01376 ENGINE = Ordinary;
11+
12+
CREATE TABLE dictdb_01376.table_for_dict
13+
(
14+
key_column UInt64,
15+
value Float64
16+
)
17+
ENGINE = Memory();
18+
19+
INSERT INTO dictdb_01376.table_for_dict VALUES (1, 1.1);
20+
21+
CREATE DICTIONARY IF NOT EXISTS dictdb_01376.dict_exists
22+
(
23+
key_column UInt64,
24+
value Float64 DEFAULT 77.77
25+
)
26+
PRIMARY KEY key_column
27+
SOURCE(CLICKHOUSE(HOST 'localhost' PORT 9000 USER 'default' TABLE 'table_for_dict' DB 'dictdb_01376'))
28+
LIFETIME(1)
29+
LAYOUT(FLAT());
30+
31+
SELECT dictGet('dictdb_01376.dict_exists', 'value', toUInt64(1)) as val FROM numbers(2) GROUP BY val;

0 commit comments

Comments
 (0)