mirror of
https://github.com/ClickHouse/ClickHouse.git
synced 2024-09-30 05:30:51 +00:00
dbms: Extended CAST to nullable types. Added ALTER TABLE ... MODIFY COLUMN ... for nullable types. A column with a nullable type can have a default value in CREATE TABLE. [#METR-19266]
This commit is contained in:
parent
4c8d5b1630
commit
9528405fe4
@ -16,10 +16,12 @@
|
||||
#include <DB/DataTypes/DataTypeEnum.h>
|
||||
#include <DB/DataTypes/DataTypeArray.h>
|
||||
#include <DB/DataTypes/DataTypeTuple.h>
|
||||
#include <DB/DataTypes/DataTypeNullable.h>
|
||||
#include <DB/Columns/ColumnString.h>
|
||||
#include <DB/Columns/ColumnFixedString.h>
|
||||
#include <DB/Columns/ColumnConst.h>
|
||||
#include <DB/Columns/ColumnArray.h>
|
||||
#include <DB/Columns/ColumnNullable.h>
|
||||
#include <DB/Core/FieldVisitors.h>
|
||||
#include <DB/Interpreters/ExpressionActions.h>
|
||||
#include <DB/Functions/IFunction.h>
|
||||
@ -1864,7 +1866,7 @@ private:
|
||||
};
|
||||
|
||||
/// Prepare nested type conversion
|
||||
const auto nested_function = prepare(from_nested_type, to_nested_type.get());
|
||||
const auto nested_function = prepareImpl(from_nested_type, to_nested_type.get());
|
||||
|
||||
return [nested_function, from_nested_type, to_nested_type] (
|
||||
Block & block, const ColumnNumbers & arguments, const size_t result)
|
||||
@ -1943,7 +1945,7 @@ private:
|
||||
|
||||
/// Create conversion wrapper for each element in tuple
|
||||
for (const auto & idx_type : ext::enumerate(from_type->getElements()))
|
||||
element_wrappers.push_back(prepare(idx_type.second, to_element_types[idx_type.first].get()));
|
||||
element_wrappers.push_back(prepareImpl(idx_type.second, to_element_types[idx_type.first].get()));
|
||||
|
||||
auto function_tuple = FunctionTuple::create(context);
|
||||
return [element_wrappers, function_tuple, from_element_types, to_element_types]
|
||||
@ -2087,7 +2089,52 @@ private:
|
||||
};
|
||||
}
|
||||
|
||||
WrapperType prepare(const DataTypePtr & from_type, const IDataType * const to_type)
|
||||
WrapperType prepare(const DataTypePtr & from_type, const IDataType * const to_type, bool unwrap_from_nullable, bool wrap_into_nullable)
|
||||
{
|
||||
auto wrapper = prepareImpl(from_type, to_type);
|
||||
|
||||
if (wrap_into_nullable)
|
||||
{
|
||||
return [wrapper, unwrap_from_nullable] (Block & block, const ColumnNumbers & arguments, const size_t result)
|
||||
{
|
||||
/// Create a temporary block on which to perform the operation.
|
||||
const auto & ret_type = block.getByPosition(result).type;
|
||||
const auto & nullable_type = static_cast<const DataTypeNullable &>(*ret_type);
|
||||
const auto & nested_type = nullable_type.getNestedType();
|
||||
|
||||
Block tmp_block = unwrap_from_nullable ? createBlockWithNestedColumns(block, arguments) : block;
|
||||
size_t tmp_res = block.columns();
|
||||
tmp_block.insert({nullptr, nested_type, ""});
|
||||
|
||||
/// Perform the requested conversion.
|
||||
wrapper(tmp_block, arguments, tmp_res);
|
||||
|
||||
/// Wrap the result into a nullable column.
|
||||
ColumnPtr null_map;
|
||||
|
||||
if (unwrap_from_nullable)
|
||||
{
|
||||
/// This is a conversion from a nullable to a nullable type.
|
||||
/// So we just keep the null map of the input argument.
|
||||
const auto & col = block.getByPosition(arguments[0]).column;
|
||||
const auto & nullable_col = static_cast<const ColumnNullable &>(*col);
|
||||
null_map = nullable_col.getNullValuesByteMap();
|
||||
}
|
||||
else
|
||||
{
|
||||
/// This is a conversion from an ordinary type to a nullable type.
|
||||
/// So we create a trivial null map.
|
||||
null_map = std::make_shared<ColumnUInt8>(block.rowsInFirstColumn(), 0);
|
||||
}
|
||||
|
||||
block.getByPosition(result).column = std::make_shared<ColumnNullable>(tmp_block.getByPosition(tmp_res).column, null_map);
|
||||
};
|
||||
}
|
||||
else
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
WrapperType prepareImpl(const DataTypePtr & from_type, const IDataType * const to_type)
|
||||
{
|
||||
if (const auto to_actual_type = typeid_cast<const DataTypeUInt8 *>(to_type))
|
||||
return createWrapper(from_type, to_actual_type);
|
||||
@ -2185,6 +2232,8 @@ public:
|
||||
|
||||
String getName() const override { return name; }
|
||||
|
||||
bool hasSpecialSupportForNulls() const override { return true; }
|
||||
|
||||
void getReturnTypeAndPrerequisitesImpl(
|
||||
const ColumnsWithTypeAndName & arguments, DataTypePtr & out_return_type,
|
||||
std::vector<ExpressionAction> & out_prerequisites) override
|
||||
@ -2201,9 +2250,38 @@ public:
|
||||
|
||||
out_return_type = DataTypeFactory::instance().get(type_col->getData());
|
||||
|
||||
wrapper_function = prepare(arguments.front().type, out_return_type.get());
|
||||
const auto & from_type = arguments.front().type;
|
||||
const DataTypePtr * from_inner_type;
|
||||
const IDataType * to_inner_type;
|
||||
|
||||
prepareMonotonicityInformation(arguments.front().type, out_return_type.get());
|
||||
bool unwrap_from_nullable = from_type->isNullable();
|
||||
bool wrap_into_nullable = out_return_type->isNullable();
|
||||
|
||||
if (wrap_into_nullable)
|
||||
{
|
||||
if (unwrap_from_nullable)
|
||||
{
|
||||
const auto & nullable_type = static_cast<const DataTypeNullable &>(*from_type);
|
||||
from_inner_type = &nullable_type.getNestedType();
|
||||
}
|
||||
else
|
||||
from_inner_type = &from_type;
|
||||
|
||||
const auto & nullable_type = static_cast<const DataTypeNullable &>(*out_return_type);
|
||||
to_inner_type = nullable_type.getNestedType().get();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (unwrap_from_nullable)
|
||||
throw Exception{"Cannot convert data from a nullable type to a non-nullable type",
|
||||
ErrorCodes::CANNOT_CONVERT_TYPE};
|
||||
|
||||
from_inner_type = &from_type;
|
||||
to_inner_type = out_return_type.get();
|
||||
}
|
||||
|
||||
wrapper_function = prepare(*from_inner_type, to_inner_type, unwrap_from_nullable, wrap_into_nullable);
|
||||
prepareMonotonicityInformation(*from_inner_type, to_inner_type);
|
||||
}
|
||||
|
||||
void executeImpl(Block & block, const ColumnNumbers & arguments, const size_t result) override
|
||||
|
@ -585,8 +585,6 @@ void MergeTreeData::checkAlter(const AlterCommands & params)
|
||||
std::begin(new_materialized_columns), std::end(new_materialized_columns));
|
||||
|
||||
createConvertExpression(nullptr, getColumnsList(), new_columns, unused_expression, unused_map, unused_bool);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void MergeTreeData::createConvertExpression(const DataPartPtr & part, const NamesAndTypesList & old_columns, const NamesAndTypesList & new_columns,
|
||||
@ -612,10 +610,10 @@ void MergeTreeData::createConvertExpression(const DataPartPtr & part, const Name
|
||||
|
||||
for (const NameAndTypePair & column : old_columns)
|
||||
{
|
||||
bool is_nullable = column.type.get()->isNullable();
|
||||
|
||||
if (!new_types.count(column.name))
|
||||
{
|
||||
bool is_nullable = column.type.get()->isNullable();
|
||||
|
||||
if (!part || part->hasColumnFiles(column.name))
|
||||
{
|
||||
/// Столбец нужно удалить.
|
||||
@ -634,6 +632,8 @@ void MergeTreeData::createConvertExpression(const DataPartPtr & part, const Name
|
||||
|
||||
if (is_nullable)
|
||||
{
|
||||
/// It is a nullable column so remove its null map
|
||||
/// and its corresponding marks file.
|
||||
out_rename_map[escaped_column + ".null"] = "";
|
||||
out_rename_map[escaped_column + ".null_mrk"] = "";
|
||||
}
|
||||
@ -696,8 +696,10 @@ void MergeTreeData::createConvertExpression(const DataPartPtr & part, const Name
|
||||
out_rename_map[escaped_expr + ".bin"] = escaped_column + ".bin";
|
||||
out_rename_map[escaped_expr + ".mrk"] = escaped_column + ".mrk";
|
||||
|
||||
if (is_nullable)
|
||||
if (new_type->isNullable())
|
||||
{
|
||||
/// The original column, whether it be nullable or not,
|
||||
/// is converted to a nullable column.
|
||||
out_rename_map[escaped_expr + ".null"] = escaped_column + ".null";
|
||||
out_rename_map[escaped_expr + ".null_mrk"] = escaped_column + ".null_mrk";
|
||||
}
|
||||
@ -878,20 +880,24 @@ void MergeTreeData::AlterDataPartTransaction::commit()
|
||||
|
||||
String path = data_part->storage.full_path + data_part->name + "/";
|
||||
|
||||
/// NOTE: checking that a file exists before renaming or deleting it
|
||||
/// is justified by the fact that, when converting an ordinary column
|
||||
/// to a nullable column, new files are created which did not exist
|
||||
/// before, i.e. they do not have older versions.
|
||||
|
||||
/// 1) Переименуем старые файлы.
|
||||
for (auto it : rename_map)
|
||||
{
|
||||
String name = it.second.empty() ? it.first : it.second;
|
||||
Poco::File(path + name).renameTo(path + name + ".tmp2");
|
||||
if (Poco::File{path + name}.exists())
|
||||
Poco::File(path + name).renameTo(path + name + ".tmp2");
|
||||
}
|
||||
|
||||
/// 2) Переместим на их место новые и обновим метаданные в оперативке.
|
||||
for (auto it : rename_map)
|
||||
{
|
||||
if (!it.second.empty())
|
||||
{
|
||||
Poco::File(path + it.first).renameTo(path + it.second);
|
||||
}
|
||||
}
|
||||
|
||||
DataPart & mutable_part = const_cast<DataPart &>(*data_part);
|
||||
@ -902,7 +908,8 @@ void MergeTreeData::AlterDataPartTransaction::commit()
|
||||
for (auto it : rename_map)
|
||||
{
|
||||
String name = it.second.empty() ? it.first : it.second;
|
||||
Poco::File(path + name + ".tmp2").remove();
|
||||
if (Poco::File{path + name + ".tmp2"}.exists())
|
||||
Poco::File(path + name + ".tmp2").remove();
|
||||
}
|
||||
|
||||
mutable_part.size_in_bytes = MergeTreeData::DataPart::calcTotalSize(path);
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <DB/DataTypes/DataTypeNullable.h>
|
||||
#include <DB/Columns/ColumnNullable.h>
|
||||
|
||||
#include <DB/Common/StringUtils.h>
|
||||
|
||||
namespace DB
|
||||
{
|
||||
@ -549,7 +550,15 @@ MergeTreeData::DataPart::Checksums MergedColumnOnlyOutputStream::writeSuffixAndG
|
||||
column_stream.second->finalize();
|
||||
if (sync)
|
||||
column_stream.second->sync();
|
||||
std::string column = escapeForFileName(column_stream.first);
|
||||
|
||||
/// Get the file basename for the given column. If this is an entry
|
||||
/// for a null map, first remove from its key the ".null" extension
|
||||
/// that was used to make this key unique.
|
||||
std::string column = column_stream.first;
|
||||
if (endsWith(column, NULL_MAP_EXTENSION))
|
||||
column = column.substr(0, column.length() - strlen(NULL_MAP_EXTENSION));
|
||||
column = escapeForFileName(column);
|
||||
|
||||
column_stream.second->addToChecksums(checksums, column);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user