mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-11-21 15:12:02 +00:00
dbms: get rid of columns inside Context [#METR-15553].
This commit is contained in:
parent
e948f1d1c1
commit
226731128b
@ -92,7 +92,6 @@ private:
|
||||
std::shared_ptr<QuotaForIntervals> quota; /// Текущая квота. По-умолчанию - пустая квота, которая ничего не ограничивает.
|
||||
String current_database; /// Текущая БД.
|
||||
String current_query_id; /// Id текущего запроса.
|
||||
NamesAndTypesList columns; /// Столбцы текущей обрабатываемой таблицы.
|
||||
Settings settings; /// Настройки выполнения запроса.
|
||||
using ProgressCallback = std::function<void(const Progress & progress)>;
|
||||
ProgressCallback progress_callback; /// Колбек для отслеживания прогресса выполнения запроса.
|
||||
@ -207,11 +206,6 @@ public:
|
||||
const Databases & getDatabases() const;
|
||||
Databases & getDatabases();
|
||||
|
||||
/// При работе со списком столбцов, используйте локальный контекст, чтобы никто больше его не менял.
|
||||
const NamesAndTypesList & getColumns() const { return columns; }
|
||||
NamesAndTypesList & getColumns() { return columns; }
|
||||
void setColumns(const NamesAndTypesList & columns_) { columns = columns_; }
|
||||
|
||||
Context & getSessionContext();
|
||||
Context & getGlobalContext();
|
||||
|
||||
|
@ -46,17 +46,18 @@ typedef std::unordered_map<String, SubqueryForSet> SubqueriesForSets;
|
||||
class ExpressionAnalyzer : private boost::noncopyable
|
||||
{
|
||||
public:
|
||||
ExpressionAnalyzer(const ASTPtr & ast_, const Context & context_, StoragePtr storage_, size_t subquery_depth_ = 0, bool do_global_ = false)
|
||||
: ast(ast_), context(context_), settings(context.getSettings()),
|
||||
subquery_depth(subquery_depth_), columns(context.getColumns()), storage(storage_ ? storage_ : getTable()), do_global(do_global_)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
||||
/// columns - список известных столбцов (которых можно достать из таблицы).
|
||||
ExpressionAnalyzer(const ASTPtr & ast_, const Context & context_, const NamesAndTypesList & columns_, size_t subquery_depth_ = 0, bool do_global_ = false)
|
||||
: ast(ast_), context(context_), settings(context.getSettings()),
|
||||
subquery_depth(subquery_depth_), columns(columns_), storage(getTable()), do_global(do_global_)
|
||||
ExpressionAnalyzer(
|
||||
const ASTPtr & ast_,
|
||||
const Context & context_,
|
||||
StoragePtr storage_,
|
||||
const NamesAndTypesList & columns_,
|
||||
size_t subquery_depth_ = 0,
|
||||
bool do_global_ = false)
|
||||
:
|
||||
ast(ast_), context(context_), settings(context.getSettings()),
|
||||
subquery_depth(subquery_depth_), columns(columns_),
|
||||
storage(storage_ ? storage_ : getTable()),
|
||||
do_global(do_global_)
|
||||
{
|
||||
init();
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ public:
|
||||
* - удалить из запроса все столбцы кроме указанных - используется для удаления ненужных столбцов из подзапросов.
|
||||
*
|
||||
* table_column_names
|
||||
* - поместить в контекст в качестве известных столбцов только указанные столбцы, а не все столбцы таблицы.
|
||||
* - список доступных столбцов таблицы.
|
||||
* Используется, например, совместно с указанием input.
|
||||
*/
|
||||
|
||||
@ -56,7 +56,7 @@ public:
|
||||
ASTPtr query_ptr_,
|
||||
const Context & context_,
|
||||
const Names & required_column_names,
|
||||
const NamesAndTypesList & table_column_names,
|
||||
const NamesAndTypesList & table_column_names_,
|
||||
QueryProcessingStage::Enum to_stage_ = QueryProcessingStage::Complete,
|
||||
size_t subquery_depth_ = 0,
|
||||
BlockInputStreamPtr input = nullptr);
|
||||
@ -84,7 +84,7 @@ public:
|
||||
private:
|
||||
/**
|
||||
* ignore_union_all_tail
|
||||
* - Оптимизация, если объект создаётся только, чтобы вызвать getSampleBlock(): учитываем только первый SELECT цепочки UNION ALL, потом что
|
||||
* - Оптимизация, если объект создаётся только, чтобы вызвать getSampleBlock(): учитываем только первый SELECT цепочки UNION ALL, потому что
|
||||
* первый SELECT достаточен для определения нужных столбцов.
|
||||
*/
|
||||
InterpreterSelectQuery(
|
||||
@ -95,8 +95,8 @@ private:
|
||||
size_t subquery_depth_ = 0,
|
||||
BlockInputStreamPtr input = nullptr);
|
||||
|
||||
void init(BlockInputStreamPtr input, const Names & required_column_names = Names(), const NamesAndTypesList & table_column_names = NamesAndTypesList());
|
||||
void basicInit(BlockInputStreamPtr input, const NamesAndTypesList & table_column_names);
|
||||
void init(BlockInputStreamPtr input, const Names & required_column_names = Names{});
|
||||
void basicInit(BlockInputStreamPtr input);
|
||||
void initQueryAnalyzer();
|
||||
|
||||
/// Выполнить один запрос SELECT из цепочки UNION ALL.
|
||||
@ -162,11 +162,12 @@ private:
|
||||
size_t subquery_depth;
|
||||
std::unique_ptr<ExpressionAnalyzer> query_analyzer;
|
||||
BlockInputStreams streams;
|
||||
NamesAndTypesList table_column_names;
|
||||
|
||||
/// Являемся ли мы первым запросом SELECT цепочки UNION ALL?
|
||||
bool is_first_select_inside_union_all;
|
||||
|
||||
/// Следующий запрос SELECT в цепочке UNION ALL.
|
||||
/// Следующий запрос SELECT в цепочке UNION ALL, если есть.
|
||||
std::unique_ptr<InterpreterSelectQuery> next_select_in_union_all;
|
||||
|
||||
/// Таблица, откуда читать данные, если не подзапрос.
|
||||
|
@ -39,7 +39,7 @@ inline void evaluateMissingDefaults(Block & block,
|
||||
* we are going to operate on a copy instead of the original block */
|
||||
Block copy_block{block};
|
||||
/// evaluate default values for defaulted columns
|
||||
ExpressionAnalyzer{default_expr_list, context, required_columns}.getActions(true)->execute(copy_block);
|
||||
ExpressionAnalyzer{default_expr_list, context, {}, required_columns}.getActions(true)->execute(copy_block);
|
||||
|
||||
/// move evaluated columns to the original block
|
||||
for (auto & column_name_type : copy_block.getColumns())
|
||||
|
@ -22,7 +22,7 @@ namespace DB
|
||||
/** pass a dummy column name because ExpressionAnalyzer
|
||||
* does not work with no columns so far. */
|
||||
ExpressionAnalyzer{
|
||||
expr, context,
|
||||
expr, context, {},
|
||||
{ { "", new DataTypeString } }
|
||||
}.getActions(false)->execute(block);
|
||||
|
||||
|
@ -152,7 +152,7 @@ bool filterBlockWithQuery(ASTPtr query, Block & block, const Context & context)
|
||||
return false;
|
||||
|
||||
/// Распарсим и вычислим выражение.
|
||||
ExpressionAnalyzer analyzer(expression_ast, context, block.getColumnsList());
|
||||
ExpressionAnalyzer analyzer(expression_ast, context, {}, block.getColumnsList());
|
||||
ExpressionActionsPtr actions = analyzer.getActions(false);
|
||||
actions->execute(block);
|
||||
|
||||
|
@ -38,9 +38,8 @@ int main(int argc, char ** argv)
|
||||
ASTPtr ast = parseQuery(parser, input.data(), input.data() + input.size(), "");
|
||||
|
||||
Context context;
|
||||
context.getColumns().push_back(NameAndTypePair("number", new DataTypeUInt64));
|
||||
|
||||
ExpressionAnalyzer analyzer(ast, context, context.getColumns());
|
||||
ExpressionAnalyzer analyzer(ast, context, {}, {NameAndTypePair("number", new DataTypeUInt64)});
|
||||
ExpressionActionsChain chain;
|
||||
analyzer.appendSelect(chain, false);
|
||||
analyzer.appendProjectResult(chain, false);
|
||||
|
@ -44,9 +44,8 @@ int main(int argc, char ** argv)
|
||||
std::cerr << ast->getTreeID() << std::endl;
|
||||
|
||||
Context context;
|
||||
context.getColumns().push_back(NameAndTypePair("number", new DataTypeUInt64));
|
||||
|
||||
ExpressionAnalyzer analyzer(ast, context, context.getColumns());
|
||||
ExpressionAnalyzer analyzer(ast, context, {}, {NameAndTypePair("number", new DataTypeUInt64)});
|
||||
ExpressionActionsChain chain;
|
||||
analyzer.appendSelect(chain, false);
|
||||
analyzer.appendProjectResult(chain, false);
|
||||
|
@ -98,8 +98,6 @@ int main(int argc, char ** argv)
|
||||
|
||||
Context context;
|
||||
|
||||
context.getColumns() = *names_and_types_list;
|
||||
|
||||
std::string input = "SELECT UniqID, URL, CounterID, IsLink WHERE URL = 'http://mail.yandex.ru/neo2/#inbox'";
|
||||
ParserSelectQuery parser;
|
||||
ASTPtr ast = parseQuery(parser, input.data(), input.data() + input.size(), "");
|
||||
@ -114,7 +112,7 @@ int main(int argc, char ** argv)
|
||||
|
||||
/// читаем из неё, применяем выражение, фильтруем, и пишем в tsv виде в консоль
|
||||
|
||||
ExpressionAnalyzer analyzer(ast, context, context.getColumns());
|
||||
ExpressionAnalyzer analyzer(ast, context, nullptr, *names_and_types_list);
|
||||
ExpressionActionsChain chain;
|
||||
analyzer.appendSelect(chain, false);
|
||||
analyzer.appendWhere(chain, false);
|
||||
|
@ -62,9 +62,8 @@ int main(int argc, char ** argv)
|
||||
std::cerr << std::endl;
|
||||
|
||||
Context context;
|
||||
context.getColumns().push_back(NameAndTypePair("number", new DataTypeUInt64));
|
||||
|
||||
ExpressionAnalyzer analyzer(ast, context, context.getColumns());
|
||||
ExpressionAnalyzer analyzer(ast, context, {}, {NameAndTypePair("number", new DataTypeUInt64)});
|
||||
ExpressionActionsChain chain;
|
||||
analyzer.appendSelect(chain, false);
|
||||
analyzer.appendProjectResult(chain, false);
|
||||
|
@ -336,7 +336,7 @@ InterpreterCreateQuery::ColumnsAndDefaults InterpreterCreateQuery::parseColumns(
|
||||
/// set missing types and wrap default_expression's in a conversion-function if necessary
|
||||
if (!defaulted_columns.empty())
|
||||
{
|
||||
const auto actions = ExpressionAnalyzer{default_expr_list, context, columns}.getActions(true);
|
||||
const auto actions = ExpressionAnalyzer{default_expr_list, context, {}, columns}.getActions(true);
|
||||
const auto block = actions->getSampleBlock();
|
||||
|
||||
for (auto & column : defaulted_columns)
|
||||
|
@ -38,7 +38,7 @@ namespace DB
|
||||
InterpreterSelectQuery::~InterpreterSelectQuery() = default;
|
||||
|
||||
|
||||
void InterpreterSelectQuery::init(BlockInputStreamPtr input, const Names & required_column_names, const NamesAndTypesList & table_column_names)
|
||||
void InterpreterSelectQuery::init(BlockInputStreamPtr input, const Names & required_column_names)
|
||||
{
|
||||
ProfileEvents::increment(ProfileEvents::SelectQuery);
|
||||
|
||||
@ -70,14 +70,14 @@ void InterpreterSelectQuery::init(BlockInputStreamPtr input, const Names & requi
|
||||
|
||||
if (is_first_select_inside_union_all && hasAsterisk())
|
||||
{
|
||||
basicInit(input, table_column_names);
|
||||
basicInit(input);
|
||||
|
||||
// Мы выполняем этот код именно здесь, потому что в противном случае следующего рода запрос бы не срабатывал:
|
||||
// SELECT X FROM (SELECT * FROM (SELECT 1 AS X, 2 AS Y) UNION ALL SELECT 3, 4)
|
||||
// из-за того, что астериски заменены столбцами только при создании объектов query_analyzer в basicInit().
|
||||
renameColumns();
|
||||
|
||||
if (!required_column_names.empty() && (context.getColumns().size() != required_column_names.size()))
|
||||
if (!required_column_names.empty() && (table_column_names.size() != required_column_names.size()))
|
||||
{
|
||||
rewriteExpressionList(required_column_names);
|
||||
/// Теперь имеется устаревшая информация для выполнения запроса. Обновляем эту информацию.
|
||||
@ -90,16 +90,16 @@ void InterpreterSelectQuery::init(BlockInputStreamPtr input, const Names & requi
|
||||
if (!required_column_names.empty())
|
||||
rewriteExpressionList(required_column_names);
|
||||
|
||||
basicInit(input, table_column_names);
|
||||
basicInit(input);
|
||||
}
|
||||
}
|
||||
|
||||
void InterpreterSelectQuery::basicInit(BlockInputStreamPtr input_, const NamesAndTypesList & table_column_names)
|
||||
void InterpreterSelectQuery::basicInit(BlockInputStreamPtr input_)
|
||||
{
|
||||
if (query.table && typeid_cast<ASTSelectQuery *>(&*query.table))
|
||||
{
|
||||
if (table_column_names.empty())
|
||||
context.setColumns(InterpreterSelectQuery::getSampleBlock(query.table, context, to_stage, subquery_depth).getColumnsList());
|
||||
table_column_names = InterpreterSelectQuery::getSampleBlock(query.table, context, to_stage, subquery_depth).getColumnsList();
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -127,16 +127,13 @@ void InterpreterSelectQuery::basicInit(BlockInputStreamPtr input_, const NamesAn
|
||||
|
||||
table_lock = storage->lockStructure(false);
|
||||
if (table_column_names.empty())
|
||||
context.setColumns(storage->getColumnsListNonMaterialized());
|
||||
table_column_names = storage->getColumnsListNonMaterialized();
|
||||
}
|
||||
|
||||
if (!table_column_names.empty())
|
||||
context.setColumns(table_column_names);
|
||||
|
||||
if (context.getColumns().empty())
|
||||
if (table_column_names.empty())
|
||||
throw Exception("There are no available columns", ErrorCodes::THERE_IS_NO_COLUMN);
|
||||
|
||||
query_analyzer.reset(new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth, true));
|
||||
query_analyzer.reset(new ExpressionAnalyzer(query_ptr, context, storage, table_column_names, subquery_depth, true));
|
||||
|
||||
/// Сохраняем в query context новые временные таблицы
|
||||
for (auto & it : query_analyzer->getExternalTables())
|
||||
@ -163,9 +160,10 @@ void InterpreterSelectQuery::basicInit(BlockInputStreamPtr input_, const NamesAn
|
||||
|
||||
void InterpreterSelectQuery::initQueryAnalyzer()
|
||||
{
|
||||
query_analyzer.reset(new ExpressionAnalyzer(query_ptr, context, storage, subquery_depth, true));
|
||||
query_analyzer.reset(new ExpressionAnalyzer(query_ptr, context, storage, table_column_names, subquery_depth, true));
|
||||
|
||||
for (auto p = next_select_in_union_all.get(); p != nullptr; p = p->next_select_in_union_all.get())
|
||||
p->query_analyzer.reset(new ExpressionAnalyzer(p->query_ptr, p->context, p->storage, p->subquery_depth, true));
|
||||
p->query_analyzer.reset(new ExpressionAnalyzer(p->query_ptr, p->context, p->storage, p->table_column_names, p->subquery_depth, true));
|
||||
}
|
||||
|
||||
InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context & context_, QueryProcessingStage::Enum to_stage_,
|
||||
@ -198,13 +196,13 @@ InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context
|
||||
|
||||
InterpreterSelectQuery::InterpreterSelectQuery(ASTPtr query_ptr_, const Context & context_,
|
||||
const Names & required_column_names_,
|
||||
const NamesAndTypesList & table_column_names, QueryProcessingStage::Enum to_stage_, size_t subquery_depth_, BlockInputStreamPtr input_)
|
||||
const NamesAndTypesList & table_column_names_, QueryProcessingStage::Enum to_stage_, size_t subquery_depth_, BlockInputStreamPtr input_)
|
||||
: query_ptr(query_ptr_), query(typeid_cast<ASTSelectQuery &>(*query_ptr)),
|
||||
context(context_), to_stage(to_stage_), subquery_depth(subquery_depth_),
|
||||
context(context_), to_stage(to_stage_), subquery_depth(subquery_depth_), table_column_names(table_column_names_),
|
||||
is_first_select_inside_union_all(query.isUnionAllHead()),
|
||||
log(&Logger::get("InterpreterSelectQuery"))
|
||||
{
|
||||
init(input_, required_column_names_, table_column_names);
|
||||
init(input_, required_column_names_);
|
||||
}
|
||||
|
||||
bool InterpreterSelectQuery::hasAsterisk() const
|
||||
|
@ -392,7 +392,7 @@ static Field convertToType(const Field & src, const IDataType & type)
|
||||
static Field evaluateConstantExpression(ASTPtr & node, const Context & context)
|
||||
{
|
||||
ExpressionActionsPtr expr_for_constant_folding = ExpressionAnalyzer(
|
||||
node, context, NamesAndTypesList{{ "_dummy", new DataTypeUInt8 }}).getConstActions();
|
||||
node, context, nullptr, NamesAndTypesList{{ "_dummy", new DataTypeUInt8 }}).getConstActions();
|
||||
|
||||
/// В блоке должен быть хотя бы один столбец, чтобы у него было известно число строк.
|
||||
Block block_with_constants{{ new ColumnConstUInt8(1, 0), new DataTypeUInt8, "_dummy" }};
|
||||
|
@ -52,9 +52,8 @@ int main(int argc, char ** argv)
|
||||
{"s1", new DataTypeString},
|
||||
{"s2", new DataTypeString}
|
||||
};
|
||||
context.setColumns(columns);
|
||||
|
||||
ExpressionAnalyzer analyzer(ast, context, context.getColumns());
|
||||
ExpressionAnalyzer analyzer(ast, context, {}, columns);
|
||||
ExpressionActionsChain chain;
|
||||
analyzer.appendSelect(chain, false);
|
||||
analyzer.appendProjectResult(chain, false);
|
||||
|
@ -296,7 +296,7 @@ namespace DB
|
||||
defaulted_columns.emplace_back(column_name, nullptr);
|
||||
}
|
||||
|
||||
const auto actions = ExpressionAnalyzer{default_expr_list, context, columns}.getActions(true);
|
||||
const auto actions = ExpressionAnalyzer{default_expr_list, context, {}, columns}.getActions(true);
|
||||
const auto block = actions->getSampleBlock();
|
||||
|
||||
/// set deduced types, modify default expression if necessary
|
||||
|
@ -103,9 +103,9 @@ MergeTreeData::MergeTreeData(
|
||||
sort_descr.push_back(SortColumnDescription(name, 1));
|
||||
}
|
||||
|
||||
primary_expr = ExpressionAnalyzer(primary_expr_ast, context, getColumnsList()).getActions(false);
|
||||
primary_expr = ExpressionAnalyzer(primary_expr_ast, context, nullptr, getColumnsList()).getActions(false);
|
||||
|
||||
ExpressionActionsPtr projected_expr = ExpressionAnalyzer(primary_expr_ast, context, getColumnsList()).getActions(true);
|
||||
ExpressionActionsPtr projected_expr = ExpressionAnalyzer(primary_expr_ast, context, nullptr, getColumnsList()).getActions(true);
|
||||
primary_key_sample = projected_expr->getSampleBlock();
|
||||
}
|
||||
else if (mode != Unsorted)
|
||||
|
@ -230,7 +230,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::read(
|
||||
filter_function = upper_filter_function;
|
||||
}
|
||||
|
||||
filter_expression = ExpressionAnalyzer(filter_function, data.context, data.getColumnsList()).getActions(false);
|
||||
filter_expression = ExpressionAnalyzer(filter_function, data.context, nullptr, data.getColumnsList()).getActions(false);
|
||||
|
||||
/// Добавим столбцы, нужные для sampling_expression.
|
||||
std::vector<String> add_columns = filter_expression->getRequiredColumns();
|
||||
@ -247,7 +247,7 @@ BlockInputStreams MergeTreeDataSelectExecutor::read(
|
||||
String prewhere_column;
|
||||
if (select.prewhere_expression)
|
||||
{
|
||||
ExpressionAnalyzer analyzer(select.prewhere_expression, data.context, data.getColumnsList());
|
||||
ExpressionAnalyzer analyzer(select.prewhere_expression, data.context, nullptr, data.getColumnsList());
|
||||
prewhere_actions = analyzer.getActions(false);
|
||||
prewhere_column = select.prewhere_expression->getColumnName();
|
||||
SubqueriesForSets prewhere_subqueries = analyzer.getSubqueriesForSets();
|
||||
@ -576,7 +576,7 @@ void MergeTreeDataSelectExecutor::createPositiveSignCondition(ExpressionActionsP
|
||||
one->type = new DataTypeInt8;
|
||||
one->value = Field(static_cast<Int64>(1));
|
||||
|
||||
out_expression = ExpressionAnalyzer(function_ptr, data.context, data.getColumnsList()).getActions(false);
|
||||
out_expression = ExpressionAnalyzer(function_ptr, data.context, {}, data.getColumnsList()).getActions(false);
|
||||
out_column = function->getColumnName();
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ PKCondition::PKCondition(ASTPtr query, const Context & context_, const NamesAndT
|
||||
/** Вычисление выражений, зависящих только от констант.
|
||||
* Чтобы индекс мог использоваться, если написано, например WHERE Date = toDate(now()).
|
||||
*/
|
||||
ExpressionActionsPtr expr_for_constant_folding = ExpressionAnalyzer(query, context_, all_columns).getConstActions();
|
||||
ExpressionActionsPtr expr_for_constant_folding = ExpressionAnalyzer(query, context_, nullptr, all_columns).getConstActions();
|
||||
Block block_with_constants;
|
||||
|
||||
/// В блоке должен быть хотя бы один столбец, чтобы у него было известно число строк.
|
||||
|
@ -64,7 +64,7 @@ StorageDistributed::StorageDistributed(
|
||||
: name(name_), columns(columns_),
|
||||
remote_database(remote_database_), remote_table(remote_table_),
|
||||
context(context_), cluster(cluster_),
|
||||
sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, *columns).getActions(false) : nullptr),
|
||||
sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, nullptr, *columns).getActions(false) : nullptr),
|
||||
sharding_key_column_name(sharding_key_ ? sharding_key_->getColumnName() : String{}),
|
||||
write_enabled(!data_path_.empty() && (cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_)),
|
||||
path(data_path_.empty() ? "" : (data_path_ + escapeForFileName(name) + '/'))
|
||||
@ -88,7 +88,7 @@ StorageDistributed::StorageDistributed(
|
||||
name(name_), columns(columns_),
|
||||
remote_database(remote_database_), remote_table(remote_table_),
|
||||
context(context_), cluster(cluster_),
|
||||
sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, *columns).getActions(false) : nullptr),
|
||||
sharding_key_expr(sharding_key_ ? ExpressionAnalyzer(sharding_key_, context, nullptr, *columns).getActions(false) : nullptr),
|
||||
sharding_key_column_name(sharding_key_ ? sharding_key_->getColumnName() : String{}),
|
||||
write_enabled(!data_path_.empty() && (cluster.getLocalNodesNum() + cluster.pools.size() < 2 || sharding_key_)),
|
||||
path(data_path_.empty() ? "" : (data_path_ + escapeForFileName(name) + '/'))
|
||||
|
Loading…
Reference in New Issue
Block a user