diff --git a/src/Columns/ColumnLowCardinality.h b/src/Columns/ColumnLowCardinality.h index 698f65b1281..faf5bb9e712 100644 --- a/src/Columns/ColumnLowCardinality.h +++ b/src/Columns/ColumnLowCardinality.h @@ -187,6 +187,7 @@ public: * So LC(Nullable(T)) would return true, LC(U) -- false. */ bool nestedIsNullable() const { return isColumnNullable(*dictionary.getColumnUnique().getNestedColumn()); } + bool nestedCanBeInsideNullable() const { return dictionary.getColumnUnique().getNestedColumn()->canBeInsideNullable(); } void nestedToNullable() { dictionary.getColumnUnique().nestedToNullable(); } void nestedRemoveNullable() { dictionary.getColumnUnique().nestedRemoveNullable(); } diff --git a/src/Interpreters/join_common.cpp b/src/Interpreters/join_common.cpp index 76bfd7f2899..e9f3e4f3fdd 100644 --- a/src/Interpreters/join_common.cpp +++ b/src/Interpreters/join_common.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -105,25 +106,57 @@ DataTypePtr convertTypeToNullable(const DataTypePtr & type) return type; } +/// Convert column to nullable. If column LowCardinality or Const, convert nested column. +/// Returns nullptr if conversion cannot be performed. +static ColumnPtr tryConvertColumnToNullable(const ColumnPtr & col) +{ + if (isColumnNullable(*col) || col->canBeInsideNullable()) + return makeNullable(col); + + if (col->lowCardinality()) + { + auto mut_col = IColumn::mutate(std::move(col)); + ColumnLowCardinality * col_lc = assert_cast(mut_col.get()); + if (col_lc->nestedIsNullable()) + { + return mut_col; + } + else if (col_lc->nestedCanBeInsideNullable()) + { + col_lc->nestedToNullable(); + return mut_col; + } + } + else if (const ColumnConst * col_const = checkAndGetColumn(*col)) + { + const auto & nested = col_const->getDataColumnPtr(); + if (nested->isNullable() || nested->canBeInsideNullable()) + { + return makeNullable(col); + } + else if (nested->lowCardinality()) + { + ColumnPtr nested_nullable = tryConvertColumnToNullable(nested); + if (nested_nullable) + return ColumnConst::create(nested_nullable, col_const->size()); + } + } + return nullptr; +} + void convertColumnToNullable(ColumnWithTypeAndName & column) { - column.type = convertTypeToNullable(column.type); - if (!column.column) + { + column.type = convertTypeToNullable(column.type); return; - - if (column.column->lowCardinality()) - { - /// Convert nested to nullable, not LowCardinality itself - auto mut_col = IColumn::mutate(std::move(column.column)); - ColumnLowCardinality * col_as_lc = assert_cast(mut_col.get()); - if (!col_as_lc->nestedIsNullable()) - col_as_lc->nestedToNullable(); - column.column = std::move(mut_col); } - else if (column.column->canBeInsideNullable()) + + ColumnPtr nullable_column = tryConvertColumnToNullable(column.column); + if (nullable_column) { - column.column = makeNullable(column.column); + column.type = convertTypeToNullable(column.type); + column.column = std::move(nullable_column); } } diff --git a/tests/queries/0_stateless/02007_join_use_nulls.reference b/tests/queries/0_stateless/02007_join_use_nulls.reference new file mode 100644 index 00000000000..30ee87bf91d --- /dev/null +++ b/tests/queries/0_stateless/02007_join_use_nulls.reference @@ -0,0 +1,8 @@ +1 2 3 1 3 +1 UInt8 2 UInt8 3 Nullable(UInt8) +1 LowCardinality(UInt8) 2 LowCardinality(UInt8) 3 LowCardinality(Nullable(UInt8)) +1 LowCardinality(UInt8) 2 LowCardinality(UInt8) 1 LowCardinality(Nullable(UInt8)) +1 UInt8 2 UInt8 3 Nullable(UInt8) +1 UInt8 2 UInt8 1 Nullable(UInt8) 3 Nullable(UInt8) +1 LowCardinality(UInt8) 2 LowCardinality(UInt8) 3 LowCardinality(Nullable(UInt8)) +1 LowCardinality(UInt8) 2 LowCardinality(UInt8) 1 LowCardinality(Nullable(UInt8)) 3 LowCardinality(Nullable(UInt8)) diff --git a/tests/queries/0_stateless/02007_join_use_nulls.sql b/tests/queries/0_stateless/02007_join_use_nulls.sql new file mode 100644 index 00000000000..e08fffce3b7 --- /dev/null +++ b/tests/queries/0_stateless/02007_join_use_nulls.sql @@ -0,0 +1,11 @@ +SET join_use_nulls = 1; + +SELECT *, d.* FROM ( SELECT 1 AS id, 2 AS value ) a SEMI LEFT JOIN ( SELECT 1 AS id, 3 AS values ) AS d USING id; + +SELECT id, toTypeName(id), value, toTypeName(value), d.values, toTypeName(d.values) FROM ( SELECT 1 AS id, 2 AS value ) a SEMI LEFT JOIN ( SELECT 1 AS id, 3 AS values ) AS d USING id; +SELECT id, toTypeName(id), value, toTypeName(value), d.values, toTypeName(d.values) FROM ( SELECT toLowCardinality(1) AS id, toLowCardinality(2) AS value ) a SEMI LEFT JOIN ( SELECT toLowCardinality(1) AS id, toLowCardinality(3) AS values ) AS d USING id; +SELECT id, toTypeName(id), value, toTypeName(value), d.id, toTypeName(d.id) FROM ( SELECT toLowCardinality(1) AS id, toLowCardinality(2) AS value ) a SEMI LEFT JOIN ( SELECT toLowCardinality(1) AS id, toLowCardinality(3) AS values ) AS d USING id; +SELECT id, toTypeName(id), value, toTypeName(value), d.values, toTypeName(d.values) FROM ( SELECT 1 AS id, 2 AS value ) a SEMI LEFT JOIN ( SELECT 1 AS id, 3 AS values ) AS d USING id; +SELECT id, toTypeName(id), value, toTypeName(value), d.id, toTypeName(d.id) , d.values, toTypeName(d.values) FROM ( SELECT 1 AS id, 2 AS value ) a SEMI LEFT JOIN ( SELECT 1 AS id, 3 AS values ) AS d USING id; +SELECT id, toTypeName(id), value, toTypeName(value), d.values, toTypeName(d.values) FROM ( SELECT toLowCardinality(1) AS id, toLowCardinality(2) AS value ) a SEMI LEFT JOIN ( SELECT toLowCardinality(1) AS id, toLowCardinality(3) AS values ) AS d USING id; +SELECT id, toTypeName(id), value, toTypeName(value), d.id, toTypeName(d.id) , d.values, toTypeName(d.values) FROM ( SELECT toLowCardinality(1) AS id, toLowCardinality(2) AS value ) a SEMI LEFT JOIN ( SELECT toLowCardinality(1) AS id, toLowCardinality(3) AS values ) AS d USING id;