| 126 | |
| 127 | |
| 128 | ColumnWithTypeAndName unwrapNullableTuple(const ColumnWithTypeAndName & column) |
| 129 | { |
| 130 | const auto * type_nullable = typeid_cast<const DataTypeNullable *>(column.type.get()); |
| 131 | if (!type_nullable) |
| 132 | return column; |
| 133 | |
| 134 | const auto * tuple_type = typeid_cast<const DataTypeTuple *>(type_nullable->getNestedType().get()); |
| 135 | if (!tuple_type) |
| 136 | return column; |
| 137 | |
| 138 | const auto & col_nullable = assert_cast<const ColumnNullable &>(*column.column); |
| 139 | |
| 140 | const auto & null_map_data = col_nullable.getNullMapData(); |
| 141 | bool has_nulls = !memoryIsZero(null_map_data.data(), 0, null_map_data.size()); |
| 142 | |
| 143 | if (!has_nulls) |
| 144 | { |
| 145 | /// No actual nulls — just strip the Nullable wrapper. |
| 146 | return {col_nullable.getNestedColumnPtr(), type_nullable->getNestedType(), column.name}; |
| 147 | } |
| 148 | |
| 149 | /// Propagate the struct null map to each Tuple element. |
| 150 | const auto & inner_tuple = assert_cast<const ColumnTuple &>(col_nullable.getNestedColumn()); |
| 151 | const auto & null_map_ptr = col_nullable.getNullMapColumnPtr(); |
| 152 | Columns new_elements; |
| 153 | DataTypes new_types; |
| 154 | for (size_t i = 0; i < tuple_type->getElements().size(); ++i) |
| 155 | { |
| 156 | auto elem_col = inner_tuple.getColumnPtr(i); |
| 157 | auto elem_type = tuple_type->getElement(i); |
| 158 | if (elem_type->isNullable()) |
| 159 | { |
| 160 | /// Element already Nullable — merge null maps (struct null OR element null). |
| 161 | const auto & existing = assert_cast<const ColumnNullable &>(*elem_col); |
| 162 | auto merged = ColumnUInt8::create(null_map_ptr->size()); |
| 163 | const auto & s = assert_cast<const ColumnUInt8 &>(*null_map_ptr).getData(); |
| 164 | const auto & e = existing.getNullMapData(); |
| 165 | auto & m = merged->getData(); |
| 166 | for (size_t j = 0; j < s.size(); ++j) |
| 167 | m[j] = s[j] | e[j]; |
| 168 | new_elements.push_back(ColumnNullable::create(existing.getNestedColumnPtr(), std::move(merged))); |
| 169 | new_types.push_back(elem_type); |
| 170 | } |
| 171 | else if (elem_type->canBeInsideNullable()) |
| 172 | { |
| 173 | new_elements.push_back(ColumnNullable::create(elem_col, null_map_ptr)); |
| 174 | new_types.push_back(std::make_shared<DataTypeNullable>(elem_type)); |
| 175 | } |
| 176 | else |
| 177 | { |
| 178 | /// Array, Map, etc. — replace values at null positions with type defaults. |
| 179 | const auto & nm = col_nullable.getNullMapData(); |
| 180 | auto mutable_col = elem_col->cloneEmpty(); |
| 181 | for (size_t j = 0; j < elem_col->size(); ++j) |
| 182 | { |
| 183 | if (nm[j]) |
| 184 | mutable_col->insertDefault(); |
| 185 | else |
no test coverage detected