Conversation
|
Create table in ice CH WRITE PATH |
xieandrew
left a comment
There was a problem hiding this comment.
An edge case and potential optimization I found, other than that it looks good.
| auto inner_type = removeNullable(sample_block->getByPosition(i).type); | ||
| if (isNothing(inner_type)) |
There was a problem hiding this comment.
It looks like this doesn't check for Nothing type inside nested types (tuple, array, or map), so those nested Nothing values are not filtered for the writer. It would be good to check if that causes the parquet writer to fail.
There was a problem hiding this comment.
Fixed, added logic to recursively remove Nothing
| filtered_columns.reserve(columns.size() - nothing_column_indices.size()); | ||
| for (size_t i = 0; i < columns.size(); ++i) | ||
| { | ||
| if (std::find(nothing_column_indices.begin(), nothing_column_indices.end(), i) == nothing_column_indices.end()) |
There was a problem hiding this comment.
The std::find on every column/chunk could removed if the constructor computes a list of column indices to keep instead of nothing_column_indices. Then you would only need to iterate the kept column indices and directly add columns[i] to filtered_columns.
Audit update for PR #2363 (Iceberg v3
|
| /// Iceberg schema metadata and is read back as NULLs on the read path. | ||
| for (size_t i = 0; i < sample_block->columns(); ++i) | ||
| { | ||
| if (!containsNothing(sample_block->getByPosition(i).type)) |
There was a problem hiding this comment.
It looks like this excludes the entire column from being written, even if other nested fields are not the Nothing type. Only the unknown field in the nested should be stripped otherwise there might be data loss.
There was a problem hiding this comment.
yes its this AI finding #2363 (comment)
|
|
||
| # This must NOT fail with UNKNOWN_TYPE. | ||
| instance.query( | ||
| f"INSERT INTO {table_name} (id, name) VALUES (3, 'charlie'), (4, 'dave')", |
There was a problem hiding this comment.
This insert should write the struct containing the unknown e.g. (42, NULL) and the next SELECT should check that the non-unknown nested field is properly written.
There was a problem hiding this comment.
instance.query(
f"INSERT INTO {table_name} (id, name, nested) VALUES (3, 'charlie', (42, NULL)), (4, 'dave', (NULL, NULL))",
settings={"allow_insert_into_iceberg": 1},
)
# ... then verifies:
result = instance.query(
f"SELECT id, name, nested.a, nested.u FROM {table_name} ORDER BY id",
).strip()
expected = (
"1\talice\t\\N\t\\N\n"
"2\tbob\t\\N\t\\N\n"
"3\tcharlie\t42\t\\N\n" # <-- 42 is preserved ✓
"4\tdave\t\\N\t\\N"
)
assert result == expected
|
I don't think these are mentioned here anywhere. Can you please check? Medium: Promoting Iceberg v3 allows Medium:
Medium: An insert into a table that has a top-level
Medium: A write that strips every column produces a Parquet file the reader rejects. If every top-level column contains |
…n column will be ignored in file stats, inserts with nothing will be rejected
…ty/ClickHouse into iceberg_unknown_data_type
|
Write Path: When the writer writes the Read Path: |
I found four confirmed defects in PR 2363's changes. Only one affects users: data compaction can't run on a v3 table that has an AI audit note: This review comment was generated by AI (Claude). Audit update for PR #2363 (Iceberg v3 Confirmed defects: Medium:
Low: Promoting
Low:
Low: A generated test config was committed
Coverage summary:
|
CI triage for
|
| Failure | Classification |
|---|---|
| Unit tests (asan_ubsan) | PR-caused — your own new gtest |
| Integration tests (amd_asan_ubsan, db disk, old analyzer, 4/8) | PR-caused — your own new test |
| GrypeScanKeeper / GrypeScanServer | Not PR-related (image CVE scan) |
| RegressionTests… aggregate_functions / cas_* / settings / s3_azure / cas_selects | Not PR-related (pre-existing/flaky testflows) |
1. Unit tests (asan_ubsan) — PR-caused, clear fix
[ RUN ] IcebergMetadataGenerator.AddColumnUnknownTypeRecordsUnknownInSchema
C++ exception with description "Column 'placeholder' of type Nullable(Nothing) maps to the
Iceberg `unknown` type, which requires format version 3, but the table uses format version 2
(set `iceberg_format_version = 3`)" thrown in the test body.
[ FAILED ] IcebergMetadataGenerator.AddColumnUnknownTypeRecordsUnknownInSchema
fail: 1, passed: 14882
This is an internal inconsistency introduced by commit 67cbdb61 ("…reject unknown on iceberg v2 tables"). That commit added checkUnknownTypeAllowed() (Utils.cpp:537), which generateAddColumnMetadata now calls (MetadataGenerator.cpp:729). But the test AddColumnUnknownTypeRecordsUnknownInSchema (gtest_iceberg_metadata_generator.cpp:517) builds its metadata with makeMetadataWithGap(), which hardcodes format_version = 2 (:60), then adds a Nullable(Nothing) column — which the new rule rejects. The test was not updated alongside the new validation.
Suggested fix (keeps the test's intent — that an unknown column records "unknown" — by putting the table on v3, where unknown is allowed):
TEST(IcebergMetadataGenerator, AddColumnUnknownTypeRecordsUnknownInSchema)
{
auto metadata = makeMetadataWithGap();
+ // Adding an `unknown`-typed column now requires format version 3 (checkUnknownTypeAllowed).
+ metadata->set(f_format_version, 3);
MetadataGenerator gen(metadata);
gen.generateAddColumnMetadata("placeholder", makeNullable(std::make_shared<DataTypeNothing>()));(Optionally also add a sibling test asserting that the same generateAddColumnMetadata call throws BAD_ARGUMENTS on a v2 table, to lock in the new rejection behavior.)
2. Integration tests (amd_asan_ubsan, db disk, old analyzer, 4/8) — PR-caused
test_storage_iceberg_with_spark/test_writes_field_ids_spark_read.py::test_writes_manifest_field_ids_spark_read (1/1184):
assert ml_ids.get("manifest_path") == 500, ml_ids
E AssertionError: {}
E assert None == 500
This test is new in this PR (added in da969e72). ml_ids came back as {}, and the earlier assert manifest_lists in _avro_metadata_schemas passed — so the snap-*.avro manifest-list was written and parsed, but its Avro writer schema carried no field-id attributes at all. That's exactly the "avro-cpp drops the field-id attributes" problem the test's own docstring describes.
Note the writer that embeds field-ids into the OCF header (IcebergWrites.cpp) is not modified by this PR, so this new test is exercising existing writer behavior for the first time. Two possibilities:
- Most likely a real gap: the manifest-list (
manifest_file) header schema isn't emitting field-ids under this build/config, so the assertion fails deterministically. If so, the manual-OCF-header path inIcebergWrites.cppneeds to cover the manifest-list schema the same way it covers the data-file/manifest schema. - Less likely: nondeterminism in avro-cpp attribute serialization.
Suggested next step: re-run this one test; if it reproduces, trace the manifest-list header serialization in IcebergWrites.cpp (the "write the OCF header directly … full JSON with field-ids intact" path) and confirm it's applied to snap-*.avro, not only to the manifests. I can dig into that code path and propose a concrete diff if you'd like — I just can't verify a writer fix without a build.
3. Not PR-related
- GrypeScanKeeper / GrypeScanServer (-alpine) — container image CVE scans ("4 high/critical" in keeper, "1" in server-alpine). These flag vulnerabilities in base-image/dependency packages, independent of any ClickHouse source change; nothing in this Iceberg-only PR affects them. Handled via the usual image/dependency bumps, not here.
- RegressionTests (testflows): aggregate_functions_1/2/3, cas_ variants, settings, s3_azure_1, cas_selects* — these suites exercise aggregate-function semantics, settings, and S3/Azure storage, none of which this PR touches (the diff is confined to
src/Storages/ObjectStorage/DataLakes/Iceberg/**plus Iceberg tests). These are the known pre-existing/flaky testflows failures on theantalya-26.6line (e.g.settingsreports 2 failed scenarios out of 1715). I classified these from scope rather than a base-branch diff — worth a re-run / compare against a base-branch run to confirm, but they are not caused by this change.
Bottom line: fix the two PR-owned tests. #1 is a one-line change (above). #2 needs a quick look at the manifest-list header field-id emission. Want me to open a blau/* PR with the unit-test fix (and investigate #2), or commit the unit-test fix directly onto iceberg_unknown_data_type?
🤖 automated CI triage — I can't build/run ClickHouse in this container, so correctness of any fix is validated by CI on re-run.
The bundled avro-cpp JSON compiler drops the Iceberg field-id/element-id
attributes, so the schema it serialized into the avro.schema header of a
ClickHouse-written manifest / manifest-list omitted them. External readers
(PyIceberg, Spark) reject such a schema during scan planning ("Cannot convert
field, missing field-id"), while ClickHouse itself reads via the Iceberg
schema metadata key and did not notice.
generateManifestFile and generateManifestList now write the original
id-carrying JSON schema string as the avro.schema header. Encoded data is
unchanged; only the header schema now carries the spec field-ids it should.
Also derive the manifest partition-struct field-id from the persisted
partition spec instead of the hardcoded 1000+i: ClickHouse numbers partition
fields from 1001, and Iceberg projects partition values by field-id, so the
previously-invisible mismatch would break external readers once the id is
emitted. Legacy v1 specs that do not track partition field-ids fall back to
the sequential 1000+i default.
Signed-off-by: Kanthi Subramanian <subkanthi@gmail.com>
a69f948 to
ae5e168
Compare
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):
Support for iceberg v3
unknowndatatype which is maps toNullable(Nothing)in Clickhouse. Read and write path(Parquet).CI/CD Options
Exclude tests:
Regression jobs to run: