forked from ClickHouse/ClickHouse
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathTableFunctionEval.cpp
More file actions
231 lines (196 loc) · 9.79 KB
/
Copy pathTableFunctionEval.cpp
File metadata and controls
231 lines (196 loc) · 9.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#include <Core/Settings.h>
#include <DataTypes/DataTypeLowCardinality.h>
#include <DataTypes/IDataType.h>
#include <Interpreters/Context.h>
#include <Interpreters/InterpreterSelectQueryAnalyzer.h>
#include <Interpreters/NormalizeSelectWithUnionQueryVisitor.h>
#include <Interpreters/QueryConstructionSettings.h>
#include <Interpreters/SelectIntersectExceptQueryVisitor.h>
#include <Interpreters/evaluateConstantExpression.h>
#include <Interpreters/executeQuery.h>
#include <Parsers/ASTCreateQuery.h>
#include <Parsers/ASTFunction.h>
#include <Parsers/ASTSelectWithUnionQuery.h>
#include <Parsers/ParserSelectWithUnionQuery.h>
#include <Parsers/parseQuery.h>
#include <Storages/StorageView.h>
#include <TableFunctions/ITableFunction.h>
#include <TableFunctions/TableFunctionFactory.h>
#include <TableFunctions/registerTableFunctions.h>
#include <Common/checkStackSize.h>
namespace DB
{
namespace Setting
{
extern const SettingsBool allow_experimental_analyzer;
extern const SettingsBool allow_experimental_eval_table_function;
extern const SettingsSetOperationMode except_default_mode;
extern const SettingsSetOperationMode intersect_default_mode;
extern const SettingsUInt64 max_parser_backtracks;
extern const SettingsUInt64 max_parser_depth;
extern const SettingsUInt64 max_query_size;
extern const SettingsSetOperationMode union_default_mode;
}
namespace ErrorCodes
{
extern const int BAD_ARGUMENTS;
extern const int ILLEGAL_TYPE_OF_ARGUMENT;
extern const int NOT_IMPLEMENTED;
extern const int NUMBER_OF_ARGUMENTS_DOESNT_MATCH;
extern const int SUPPORT_IS_DISABLED;
}
namespace
{
/** eval('SELECT 1') - a table function that evaluates its argument to a query string
* and executes that string as a single SELECT query.
*
* The argument is an arbitrary constant expression, so the query text can also be
* produced by a scalar subquery: eval((SELECT query FROM config)). The parser
* additionally accepts a bare query, eval(SELECT ...), as syntactic sugar for
* a scalar subquery argument.
*
* The generated query is wrapped into StorageView, same as the `view` table function.
*/
class TableFunctionEval : public ITableFunction
{
public:
static constexpr auto name = "eval";
std::string getName() const override { return name; }
/// `eval` must not be persisted through `CREATE TABLE ... AS eval(...)`: the source expression
/// would be re-evaluated on every `ATTACH`, so such a table could fail to attach after a restart
/// (the experimental setting might be disabled) or silently change if the expression depends on
/// parameters, settings, or time. There is no stable persisted representation, so forbid it.
bool canBeUsedToCreateTable() const override { return false; }
private:
StoragePtr executeImpl(
const ASTPtr & ast_function,
ContextPtr context,
const std::string & table_name,
ColumnsDescription cached_columns,
bool is_insert_query) const override;
const char * getStorageEngineName() const override { return "View"; }
void parseArguments(const ASTPtr & ast_function, ContextPtr context) override;
ColumnsDescription getActualTableStructure(ContextPtr context, bool is_insert_query) const override;
ASTCreateQuery create;
};
/// The generated query must be self-contained, and in particular it cannot use `eval` again:
/// otherwise queries could recurse through generated query texts.
void checkNoEval(const ASTPtr & ast)
{
if (const auto * function = ast->as<ASTFunction>(); function && function->name == TableFunctionEval::name)
throw Exception(
ErrorCodes::BAD_ARGUMENTS, "The query generated by table function `eval` cannot use `eval` itself");
for (const auto & child : ast->children)
checkNoEval(child);
}
void TableFunctionEval::parseArguments(const ASTPtr & ast_function, ContextPtr context)
{
const auto & settings = context->getSettingsRef();
if (!settings[Setting::allow_experimental_eval_table_function])
throw Exception(
ErrorCodes::SUPPORT_IS_DISABLED,
"Table function `eval` is experimental. Set `allow_experimental_eval_table_function = 1` to enable it");
if (!settings[Setting::allow_experimental_analyzer])
throw Exception(
ErrorCodes::NOT_IMPLEMENTED,
"Table function `eval` is supported only with the analyzer. Set `enable_analyzer = 1` to use it");
/// The generated query can still reach `eval` indirectly, for example through the body
/// of a SQL user defined function, so recursion is bounded by the stack size check.
checkStackSize();
const auto * function = ast_function->as<ASTFunction>();
if (!function || !function->arguments || function->arguments->children.size() != 1)
throw Exception(
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH,
"Table function `eval` requires exactly one argument: a constant expression returning a query string");
const auto & [value, type] = evaluateConstantExpression(function->arguments->children[0], context);
if (!isString(removeLowCardinalityAndNullable(type)))
throw Exception(
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT,
"Table function `eval` requires the argument of type String (possibly Nullable or LowCardinality), got {}",
type->getName());
if (value.isNull())
throw Exception(ErrorCodes::BAD_ARGUMENTS, "The query text passed to table function `eval` cannot be NULL");
const auto & query_text = value.safeGet<String>();
ParserSelectWithUnionQuery parser;
ASTPtr query = parseQuery(
parser,
query_text.data(),
query_text.data() + query_text.size(),
"query generated by table function `eval`",
settings[Setting::max_query_size],
settings[Setting::max_parser_depth],
settings[Setting::max_parser_backtracks]);
checkNoEval(query);
/// The generated query cannot flip the `enable_analyzer` setting in a SETTINGS clause:
/// `eval` is analyzer-only, and the same validation rejects such a change for a usual query.
validateAnalyzerSettings(query, settings[Setting::allow_experimental_analyzer]);
/// The generated query does not go through `executeQuery`, so materialize the construction
/// settings a NON-last `UNION` arm carries in its own `SETTINGS` clause here, same as
/// `executeQueryImpl` does for a usual query (and in the same order: before the `UNION`
/// normalization visitors and before `wrapNestedConstructionSettings`). Without this the first
/// arm's settings in e.g. `eval('(SELECT … SETTINGS limit = 1) UNION ALL SELECT …')` would be
/// consumed by `takeNestedConstructionSettings` and re-scoped to the whole union, and the
/// ambiguous mix of non-last-arm and last-arm construction `SETTINGS` would not be rejected.
wrapPerArmConstructionSettings(
query,
settings[Setting::max_query_size],
settings[Setting::max_parser_depth],
settings[Setting::max_parser_backtracks]);
/// The generated query does not go through `executeQuery`, so resolve the INTERSECT/EXCEPT
/// operator precedence and the implicit UNION mode here, same as `executeQueryImpl` does
/// for a usual query.
{
SelectIntersectExceptQueryVisitor::Data data{
settings[Setting::intersect_default_mode], settings[Setting::except_default_mode]};
SelectIntersectExceptQueryVisitor{data}.visit(query);
}
{
NormalizeSelectWithUnionQueryVisitor::Data data{settings[Setting::union_default_mode]};
NormalizeSelectWithUnionQueryVisitor{data}.visit(query);
}
/// The generated query does not go through `executeQuery`, so materialize the query-construction
/// settings (`limit` / `offset` / `page` / `select` / `filter` / `order` / `sort`) it carries in
/// its own `SETTINGS` clause here, same as `executeQueryImpl` does for a usual query. Without this
/// they would be silently dropped: `QueryTreeBuilder` removes `limit` / `offset` from a query's
/// `SETTINGS` clause (expecting them already materialized into an outer `LIMIT` / `OFFSET`), so
/// e.g. `eval('SELECT number FROM numbers(3) SETTINGS limit = 1')` would ignore the limit. Only the
/// generated query's own `SETTINGS` clause is applied (its scope) — the session/user construction
/// settings shape the outer query that reads from `eval`, not the generated query.
wrapNestedConstructionSettings(
query,
settings[Setting::max_query_size],
settings[Setting::max_parser_depth],
settings[Setting::max_parser_backtracks]);
create.set(create.select, query);
}
ColumnsDescription TableFunctionEval::getActualTableStructure(ContextPtr context, bool /*is_insert_query*/) const
{
chassert(create.select);
chassert(create.children.size() == 1);
chassert(create.children[0]->as<ASTSelectWithUnionQuery>());
auto sample_block = InterpreterSelectQueryAnalyzer::getSampleBlock(create.children[0], context);
return ColumnsDescription(sample_block->getNamesAndTypesList());
}
StoragePtr TableFunctionEval::executeImpl(
const ASTPtr & /*ast_function*/,
ContextPtr context,
const std::string & table_name,
ColumnsDescription /*cached_columns*/,
bool is_insert_query) const
{
auto columns = getActualTableStructure(context, is_insert_query);
auto res = std::make_shared<StorageView>(StorageID(getDatabaseName(), table_name), create, columns, "");
res->startup();
return res;
}
}
void registerTableFunctionEval(TableFunctionFactory & factory)
{
factory.registerFunction<TableFunctionEval>(
{
.description = R"(Evaluates a constant expression to a query string and executes the resulting `SELECT` query.)",
.category = FunctionDocumentation::Category::TableFunction,
},
{.allow_readonly = true});
}
}