clickhouse: ExpressionAnalyzer: development [#CONV-7444].

This commit is contained in:
Michael Kolupaev 2013-05-29 11:46:51 +00:00
parent 93d92e2d40
commit 813c79061a
6 changed files with 262 additions and 77 deletions

View File

@ -194,6 +194,7 @@ namespace ErrorCodes
UNSUPPORTED_COLLATION_LOCALE,
COLLATION_COMPARISON_FAILED,
UNKNOWN_ACTION,
MULTIPLE_ARRAY_JOIN,
POCO_EXCEPTION = 1000,
STD_EXCEPTION,

View File

@ -12,7 +12,7 @@ typedef std::vector<NameWithAlias> NamesWithAliases;
/** Содержит последовательность действий над блоком.
*/
class ExpressionActions : private boost::noncopyable
class ExpressionActions
{
public:
struct Action

View File

@ -18,27 +18,36 @@ namespace DB
class ExpressionAnalyzer : private boost::noncopyable
{
public:
ExpressionAnalyzer(const ASTPtr & ast_, const Context & context_)
: ast(ast_), context(context_), settings(context.getSettings()), columns(context.getColumns()), storage(getTable())
ExpressionAnalyzer(const ASTPtr & ast_, const Context & context_, size_t subquery_depth_ = 0)
: ast(ast_), context(context_), settings(context.getSettings()),
subquery_depth(subquery_depth_), columns(context.getColumns()), storage(getTable())
{
init();
}
/// columns - список известных столбцов (которых можно достать из таблицы).
ExpressionAnalyzer(const ASTPtr & ast_, const Context & context_, const NamesAndTypesList & columns_)
: ast(ast_), context(context_), settings(context.getSettings()), columns(columns_), storage(getTable())
ExpressionAnalyzer(const ASTPtr & ast_, const Context & context_, const NamesAndTypesList & columns_, size_t subquery_depth_ = 0)
: ast(ast_), context(context_), settings(context.getSettings()),
subquery_depth(subquery_depth_), columns(columns_), storage(getTable())
{
init();
}
/// Есть ли в выражении arrayJoin.
bool hasArrayJoin() { return has_array_join; }
/// Получить имя столбца, находящегося внутри arrayJoin.
void getArrayJoinInfo(String & column_name) { column_name = column_under_array_join.first; }
/// Есть ли в выражении агрегатные функции или секция GROUP BY или HAVING.
bool hasAggregation() { return has_aggregation; }
/// Получить список ключей агрегирования и описаний агрегатных функций, если в запросе есть GROUP BY.
void getAggregateInfo(Names & key_names, AggregateDescriptions & aggregates);
/** Эти методы позволяют собрать цепочку преобразований над блоком, получающую значения в нужных секциях запроса.
* Выполняют подзапросы в соответствующих частях запроса.
*
* Пример использования:
* ExpressionActionsChain chain;
@ -49,6 +58,9 @@ public:
* chain.finalize();
*/
/// До arrayJoin.
void appendArrayJoinArgument(ExpressionActionsChain & chain);
/// До агрегации:
bool appendWhere(ExpressionActionsChain & chain);
bool appendGroupBy(ExpressionActionsChain & chain);
@ -78,9 +90,14 @@ private:
ASTSelectQuery * select_query;
const Context & context;
Settings settings;
size_t subquery_depth;
/// Известные столбцы.
/// Исходные столбцы.
NamesAndTypesList columns;
/// Столбцы после выполнения arrayJoin. Если нет arrayJoin, совпадает с columns.
NamesAndTypesList array_joined_columns;
/// Столбцы после агрегации. Если нет агрегации, совпадает с array_joined_columns.
NamesAndTypesList aggregated_columns;
/// Таблица, из которой делается запрос. Используется для sign-rewrite'а
const StoragePtr storage;
@ -90,7 +107,9 @@ private:
bool has_aggregation;
NamesAndTypesList aggregation_keys;
AggregateDescriptions aggregate_descriptions;
NamesAndTypesList aggregated_columns; /// Если нет агрегации, совпадает с columns.
bool has_array_join;
NameAndTypePair column_under_array_join;
typedef std::map<String, ASTPtr> Aliases;
Aliases aliases;
@ -100,7 +119,8 @@ private:
void init();
NamesAndTypesList::iterator findColumn(const String & name);
NamesAndTypesList::iterator findColumn(const String & name, NamesAndTypesList & cols);
NamesAndTypesList::iterator findColumn(const String & name) { return findColumn(name, columns); }
/** Создать словарь алиасов.
*/
@ -113,16 +133,22 @@ private:
void normalizeTree();
void normalizeTreeImpl(ASTPtr & ast, MapOfASTs & finished_asts, SetOfASTs & current_asts);
/// Превратить перечисление значений или подзапрос в ASTSet. ast - функция in или notIn.
void makeSet(ASTPtr ast);
/// Превратить перечисление значений или подзапрос в ASTSet. node - функция in или notIn.
void makeSet(ASTFunction * node, ExpressionActions & actions);
void getActionsImpl(ASTPtr ast, bool no_subqueries, bool only_consts, ExpressionActions & actions);
void getActionsBeforeAggregationImpl(ASTPtr ast, ExpressionActions * actions, Names * result_columns);
void getActionsBeforeAggregationImpl(ASTPtr ast, ExpressionActions & actions);
void getActionsBeforeArrayJoinImpl(ASTPtr ast, ExpressionActions & actions);
/// Добавить агрегатные функции в aggregate_descriptions.
/// Установить has_aggregation=true, если есть хоть одна агрегатная функция.
void getAggregatesImpl(ASTPtr ast, ExpressionActions & actions);
/// Если есть arrayJoin, установить has_array_join=true и column_under_array_join.
void getArrayJoinImpl(ASTPtr ast, ExpressionActions & actions);
/// Получить таблицу, из которой идет запрос
StoragePtr getTable();
@ -148,6 +174,7 @@ private:
void assertSelect();
void assertAggregation();
void assertArrayJoin();
};
}

View File

@ -14,6 +14,15 @@ namespace DB
class ASTFunction : public IAST
{
public:
enum FunctionKind
{
UNKNOWN,
FUNCTION,
AGGREGATE_FUNCTION,
LAMBDA_EXPRESSION,
ARRAY_JOIN,
};
/// имя функции
String name;
/// аргументы
@ -23,8 +32,7 @@ public:
/// алиас, если есть
String alias;
bool is_aggregate_function;
bool is_lambda_expression;
FunctionKind kind;
/// сама функция
FunctionPtr function;
@ -35,8 +43,8 @@ public:
/// тип возвращаемого значения
DataTypePtr return_type;
ASTFunction() : is_aggregate_function(false), is_lambda_expression(false) {}
ASTFunction(StringRange range_) : IAST(range_), is_aggregate_function(false), is_lambda_expression(false) {}
ASTFunction() : kind(UNKNOWN) {}
ASTFunction(StringRange range_) : IAST(range_), kind(UNKNOWN) {}
String getColumnName() const
{

View File

@ -1,4 +1,6 @@
#include <DB/Interpreters/ExpressionActions.h>
#include <DB/Columns/ColumnsNumber.h>
#include <DB/Functions/FunctionsMiscellaneous.h>
#include <set>
namespace DB
@ -19,7 +21,20 @@ void ExpressionActions::Action::prepare(Block & sample_block)
types[i] = sample_block.getByName(argument_names[i]).type;
}
result_type = function->getReturnType(types);
if (FunctionTupleElement * func_tuple_elem = dynamic_cast<FunctionTupleElement *>(&*function))
{
/// Особый случай - для функции tupleElement обычный метод getReturnType не работает.
if (argument_names.size() != 2)
throw Exception("Function tupleElement requires exactly two arguments: tuple and element index.",
ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
const ColumnConstUInt8 * index_col = dynamic_cast<const ColumnConstUInt8 *>(&*sample_block.getByName(argument_names[1]).column);
result_type = func_tuple_elem->getReturnType(types, index_col->getData());
}
else
{
result_type = function->getReturnType(types);
}
sample_block.insert(ColumnWithNameAndType(NULL, result_type, result_name));
}
@ -28,7 +43,7 @@ void ExpressionActions::Action::prepare(Block & sample_block)
if (sample_block.has(result_name))
throw Exception("Column '" + result_name + "' already exists", ErrorCodes::DUPLICATE_COLUMN);
sample_block.insert(ColumnWithNameAndType(NULL, result_type, result_name));
sample_block.insert(ColumnWithNameAndType(added_column, result_type, result_name));
}
else
{

View File

@ -57,19 +57,35 @@ void ExpressionAnalyzer::init()
createAliasesDict(ast); /// Если есть агрегатные функции, присвоит has_aggregation=true.
normalizeTree();
array_joined_columns = columns;
/// Найдем arrayJoin.
ExpressionActions temp_actions(columns, settings);
getArrayJoinImpl(ast, temp_actions);
if (has_array_join)
{
assertSelect();
NameAndTypePair col;
col.first = "arrayJoin(" + column_under_array_join.first + ")";
const DataTypeArray * array = dynamic_cast<const DataTypeArray *>(&*column_under_array_join.second);
if (!array)
throw Exception("Argument of arrayJoin must be an array", ErrorCodes::TYPE_MISMATCH);
col.second = array->getNestedType();
array_joined_columns.push_back(col);
}
/// Найдем агрегатные функции.
if (select_query && (select_query->group_expression_list || select_query->having_expression))
has_aggregation = true;
temp_actions = ExpressionActions(array_joined_columns, settings);
getAggregatesImpl(ast, temp_actions);
if (has_aggregation)
{
if (!select_query)
throw Exception("Aggregation not in select query", ErrorCodes::ILLEGAL_AGGREGATION);
/// Действия до агрегации. Используются только для определения типов.
ExpressionActions actions(columns, settings);
/// Найдем агрегатные функции.
getAggregatesImpl(ast, actions);
assertSelect();
/// Найдем ключи агрегации.
if (select_query->group_expression_list)
@ -77,10 +93,10 @@ void ExpressionAnalyzer::init()
const ASTs & group_asts = select_query->group_expression_list->children;
for (size_t i = 0; i < group_asts.size(); ++i)
{
getActionsImpl(group_asts[i], true, false, actions);
getActionsImpl(group_asts[i], true, false, temp_actions);
NameAndTypePair key;
key.first = group_asts[i]->getColumnName();
key.second = actions.getSampleBlock().getByName(key.first).type;
key.second = temp_actions.getSampleBlock().getByName(key.first).type;
aggregation_keys.push_back(key);
}
}
@ -94,15 +110,15 @@ void ExpressionAnalyzer::init()
}
else
{
aggregated_columns = columns;
aggregated_columns = array_joined_columns;
}
}
NamesAndTypesList::iterator ExpressionAnalyzer::findColumn(const String & name)
NamesAndTypesList::iterator ExpressionAnalyzer::findColumn(const String & name, NamesAndTypesList & cols)
{
NamesAndTypesList::iterator it;
for (it = columns.begin(); it != columns.end(); ++it)
for (it = cols.begin(); it != cols.end(); ++it)
if (it->first == name)
break;
return it;
@ -305,23 +321,6 @@ void ExpressionAnalyzer::normalizeTreeImpl(ASTPtr & ast, MapOfASTs & finished_as
ASTPtr initial_ast = ast;
current_asts.insert(initial_ast);
/// rewrite правила, которые действуют при обходе сверху-вниз.
if (ASTFunction * node = dynamic_cast<ASTFunction *>(&*ast))
{
/** Нет ли в таблице столбца, название которого полностью совпадает с записью функции?
* Например, в таблице есть столбец "domain(URL)", и мы запросили domain(URL).
*/
String function_string = node->getColumnName();
NamesAndTypesList::const_iterator it = findColumn(function_string);
if (columns.end() != it)
{
ASTIdentifier * ast_id = new ASTIdentifier(node->range, std::string(node->range.first, node->range.second));
ast_id->type = it->second;
ast = ast_id;
current_asts.insert(ast);
}
}
/// Обход снизу-вверх. Не опускаемся в подзапросы.
for (ASTs::iterator it = ast->children.begin(); it != ast->children.end(); ++it)
@ -352,15 +351,24 @@ void ExpressionAnalyzer::normalizeTreeImpl(ASTPtr & ast, MapOfASTs & finished_as
{
if (node->name == "lambda")
{
node->is_lambda_expression = true;
node->kind = ASTFunction::LAMBDA_EXPRESSION;
}
else if (context.getAggregateFunctionFactory().isAggregateFunctionName(node->name))
{
node->is_aggregate_function = true;
node->kind = ASTFunction::AGGREGATE_FUNCTION;
has_aggregation = true;
if (!sign_column_name.empty())
considerSignRewrite(ast);
}
else if (node->name == "arrayJoin")
{
has_array_join = true;
node->kind = ASTFunction::ARRAY_JOIN;
}
else
{
node->kind = ASTFunction::FUNCTION;
}
}
else if (ASTIdentifier * node = dynamic_cast<ASTIdentifier *>(&*ast))
{
@ -393,10 +401,59 @@ void ExpressionAnalyzer::normalizeTreeImpl(ASTPtr & ast, MapOfASTs & finished_as
}
/// ast - ASTFunction с названием in или notIn.
void ExpressionAnalyzer::makeSet(ASTPtr ast)
void ExpressionAnalyzer::makeSet(ASTFunction * node, ExpressionActions & actions)
{
throw Exception("Not implemented", ErrorCodes::NOT_IMPLEMENTED);
/** Нужно преобразовать правый аргумент в множество.
* Это может быть перечисление значений или подзапрос.
* Перечисление значений парсится как функция tuple.
*/
IAST & args = *node->arguments;
ASTPtr & arg = args.children[1];
if (dynamic_cast<ASTSubquery *>(&*arg))
{
/// Исполняем подзапрос, превращаем результат в множество, и кладём это множество на место подзапроса.
InterpreterSelectQuery interpreter(arg->children[0], context, QueryProcessingStage::Complete, subquery_depth + 1);
ASTSet * ast_set = new ASTSet(arg->getColumnName());
ast_set->set = new Set;
ast_set->set->create(interpreter.execute());
arg = ast_set;
}
else if (ASTFunction * set_func = dynamic_cast<ASTFunction *>(&*arg))
{
/// Случай явного перечисления значений.
if (set_func->name != "tuple")
throw Exception("Incorrect type of 2nd argument for function " + node->name + ". Must be subquery or set of values.",
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
DataTypes set_element_types;
ASTPtr & left_arg = args.children[0];
ASTFunction * left_arg_tuple = dynamic_cast<ASTFunction *>(&*left_arg);
if (left_arg_tuple && left_arg_tuple->name == "tuple")
{
for (ASTs::const_iterator it = left_arg_tuple->arguments->children.begin();
it != left_arg_tuple->arguments->children.end();
++it)
set_element_types.push_back(actions.getSampleBlock().getByName((*it)->getColumnName()).type);
}
else
{
DataTypePtr left_type = actions.getSampleBlock().getByName(left_arg->getColumnName()).type;
if (DataTypeArray * array_type = dynamic_cast<DataTypeArray *>(&*left_type))
set_element_types.push_back(array_type->getNestedType());
else
set_element_types.push_back(left_type);
}
ASTSet * ast_set = new ASTSet(arg->getColumnName());
ast_set->set = new Set;
ast_set->set->create(set_element_types, set_func->arguments);
arg = ast_set;
}
else if (!dynamic_cast<ASTSet *>(&*arg))
throw Exception("Incorrect type of 2nd argument for function " + node->name + ". Must be subquery or set of values.",
ErrorCodes::ILLEGAL_TYPE_OF_ARGUMENT);
}
@ -418,17 +475,19 @@ void ExpressionAnalyzer::getActionsImpl(ASTPtr ast, bool no_subqueries, bool onl
if (ASTFunction * node = dynamic_cast<ASTFunction *>(&*ast))
{
if (node->is_lambda_expression)
if (node->kind == ASTFunction::LAMBDA_EXPRESSION)
throw Exception("Unexpected expression", ErrorCodes::UNEXPECTED_EXPRESSION);
if (!node->is_aggregate_function)
if (node->kind == ASTFunction::FUNCTION)
{
if (node->name == "in" || node->name == "notIn")
{
if (!no_subqueries)
{
/// Найдем тип первого аргумента (потом getActionsImpl вызовется для него снова и ни на что не повлияет).
getActionsImpl(node->arguments->children[0], no_subqueries, only_consts, actions);
/// Превратим tuple или подзапрос в множество.
makeSet(ast);
makeSet(node, actions);
}
else
{
@ -500,7 +559,8 @@ void ExpressionAnalyzer::getActionsImpl(ASTPtr ast, bool no_subqueries, bool onl
ASTFunction * lambda_args_tuple = dynamic_cast<ASTFunction *>(&*lambda->arguments->children[0]);
ASTs lambda_arg_asts = lambda_args_tuple->arguments->children;
NamesAndTypes lambda_args;
NamesAndTypesList initial_columns = columns;
NamesAndTypesList lambda_columns = actions.getRequiredColumnsWithTypes();
for (size_t j = 0; j < lambda_arg_asts.size(); ++j)
{
@ -511,20 +571,18 @@ void ExpressionAnalyzer::getActionsImpl(ASTPtr ast, bool no_subqueries, bool onl
String arg_name = identifier->name;
NameAndTypePair arg(arg_name, lambda_type->getArgumentTypes()[j]);
NamesAndTypesList::iterator it = findColumn(arg_name);
if (it != columns.end())
NamesAndTypesList::iterator it = findColumn(arg_name, lambda_columns);
if (it != lambda_columns.end())
it->second = arg.second;
else
columns.push_front(arg);
lambda_columns.push_back(arg);
lambda_args.push_back(arg);
}
ExpressionActionsPtr lambda_actions = new ExpressionActions(columns, settings);
ExpressionActionsPtr lambda_actions = new ExpressionActions(lambda_columns, settings);
getActionsImpl(lambda->arguments->children[1], no_subqueries, only_consts, *lambda_actions);
columns = initial_columns;
String result_name = lambda->arguments->children[1]->getColumnName();
lambda_actions->finalize(Names(1, result_name));
DataTypePtr result_type = lambda_actions->getSampleBlock().getByName(result_name).type;
@ -586,8 +644,9 @@ void ExpressionAnalyzer::getActionsImpl(ASTPtr ast, bool no_subqueries, bool onl
void ExpressionAnalyzer::getAggregatesImpl(ASTPtr ast, ExpressionActions & actions)
{
ASTFunction * node = dynamic_cast<ASTFunction *>(&*ast);
if (node && node->is_aggregate_function)
if (node && node->kind == ASTFunction::AGGREGATE_FUNCTION)
{
has_aggregation = true;
AggregateDescription aggregate;
aggregate.column_name = node->getColumnName();
@ -642,6 +701,39 @@ void ExpressionAnalyzer::getAggregatesImpl(ASTPtr ast, ExpressionActions & actio
}
}
void ExpressionAnalyzer::getArrayJoinImpl(ASTPtr ast, ExpressionActions & actions)
{
ASTFunction * node = dynamic_cast<ASTFunction *>(&*ast);
if (node && node->kind == ASTFunction::ARRAY_JOIN)
{
ASTs & arguments = node->arguments->children;
if (arguments.size() != 1)
throw Exception("arrayJoin requires exactly 1 argument", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
String name = arguments[0]->getColumnName();
if (has_array_join)
{
if (column_under_array_join.first != name)
throw Exception("Multiple arrayJoin in single query", ErrorCodes::MULTIPLE_ARRAY_JOIN);
}
else
{
has_array_join = true;
getActionsImpl(arguments[0], true, false, actions);
column_under_array_join.first = name;
column_under_array_join.second = actions.getSampleBlock().getByName(name).type;
}
}
else
{
for (size_t i = 0; i < ast->children.size(); ++i)
{
getArrayJoinImpl(ast->children[i], actions);
}
}
}
void ExpressionAnalyzer::assertSelect()
{
if (!select_query)
@ -652,6 +744,11 @@ void ExpressionAnalyzer::assertAggregation()
if (!has_aggregation)
throw Exception("No aggregation", ErrorCodes::LOGICAL_ERROR);
}
void ExpressionAnalyzer::assertArrayJoin()
{
if (!has_array_join)
throw Exception("No arrayJoin", ErrorCodes::LOGICAL_ERROR);
}
void ExpressionAnalyzer::initChain(ExpressionActionsChain & chain, NamesAndTypesList & columns)
{
@ -662,6 +759,17 @@ void ExpressionAnalyzer::initChain(ExpressionActionsChain & chain, NamesAndTypes
}
}
void ExpressionAnalyzer::appendArrayJoinArgument(ExpressionActionsChain & chain)
{
assertArrayJoin();
initChain(chain, columns);
ExpressionActionsChain::Step & step = chain.steps.back();
step.required_output.push_back(column_under_array_join.first);
getActionsBeforeArrayJoinImpl(ast, *step.actions);
}
bool ExpressionAnalyzer::appendWhere(ExpressionActionsChain & chain)
{
assertSelect();
@ -669,7 +777,7 @@ bool ExpressionAnalyzer::appendWhere(ExpressionActionsChain & chain)
if (!select_query->where_expression)
return false;
initChain(chain, columns);
initChain(chain, array_joined_columns);
ExpressionActionsChain::Step & step = chain.steps.back();
step.required_output.push_back(select_query->where_expression->getColumnName());
@ -685,7 +793,7 @@ bool ExpressionAnalyzer::appendGroupBy(ExpressionActionsChain & chain)
if (!select_query->group_expression_list)
return false;
initChain(chain, columns);
initChain(chain, array_joined_columns);
ExpressionActionsChain::Step & step = chain.steps.back();
ASTs asts = select_query->group_expression_list->children;
@ -702,16 +810,24 @@ void ExpressionAnalyzer::appendAggregateFunctionsArguments(ExpressionActionsChai
{
assertAggregation();
initChain(chain, columns);
initChain(chain, array_joined_columns);
ExpressionActionsChain::Step & step = chain.steps.back();
getActionsBeforeAggregationImpl(select_query->select_expression_list, &*step.actions, &step.required_output);
for (size_t i = 0; i < aggregate_descriptions.size(); ++i)
{
for (size_t j = 0; j < aggregate_descriptions[i].argument_names.size(); ++j)
{
step.required_output.push_back(aggregate_descriptions[i].argument_names[j]);
}
}
getActionsBeforeAggregationImpl(select_query->select_expression_list, *step.actions);
if (select_query->having_expression)
getActionsBeforeAggregationImpl(select_query->having_expression, &*step.actions, &step.required_output);
getActionsBeforeAggregationImpl(select_query->having_expression, *step.actions);
if (select_query->order_expression_list)
getActionsBeforeAggregationImpl(select_query->order_expression_list, &*step.actions, &step.required_output);
getActionsBeforeAggregationImpl(select_query->order_expression_list, *step.actions);
}
bool ExpressionAnalyzer::appendHaving(ExpressionActionsChain & chain)
@ -791,26 +907,44 @@ void ExpressionAnalyzer::appendProjectResult(DB::ExpressionActionsChain & chain)
}
void ExpressionAnalyzer::getActionsBeforeAggregationImpl(ASTPtr ast, ExpressionActions * actions, Names * result_columns)
void ExpressionAnalyzer::getActionsBeforeAggregationImpl(ASTPtr ast, ExpressionActions & actions)
{
ASTFunction * node = dynamic_cast<ASTFunction *>(&*ast);
if (node && node->is_aggregate_function)
if (node && node->kind == ASTFunction::AGGREGATE_FUNCTION)
{
ASTs & arguments = node->arguments->children;
for (size_t i = 0; i < arguments.size(); ++i)
{
if (result_columns)
result_columns->push_back(arguments[i]->getColumnName());
if (actions)
getActionsImpl(arguments[i], false, false, *actions);
getActionsImpl(arguments[i], false, false, actions);
}
}
else
{
for (size_t i = 0; i < ast->children.size(); ++i)
{
getActionsBeforeAggregationImpl(ast->children[i], actions, result_columns);
getActionsBeforeAggregationImpl(ast->children[i], actions);
}
}
}
void ExpressionAnalyzer::getActionsBeforeArrayJoinImpl(ASTPtr ast, ExpressionActions & actions)
{
ASTFunction * node = dynamic_cast<ASTFunction *>(&*ast);
if (node && node->kind == ASTFunction::ARRAY_JOIN)
{
ASTs & arguments = node->arguments->children;
if (arguments.size() != 1)
throw Exception("arrayJoin requires exactly 1 argument", ErrorCodes::NUMBER_OF_ARGUMENTS_DOESNT_MATCH);
getActionsImpl(arguments[0], false, false, actions);
}
else
{
for (size_t i = 0; i < ast->children.size(); ++i)
{
getActionsBeforeArrayJoinImpl(ast->children[i], actions);
}
}
}