Skip to content

[feature](iceberg) Add Iceberg V3 spatial type support - #67907

Open
OIiveirra wants to merge 41 commits into
apache:branch-4.1from
OIiveirra:feature/doris-28228
Open

[feature](iceberg) Add Iceberg V3 spatial type support#67907
OIiveirra wants to merge 41 commits into
apache:branch-4.1from
OIiveirra:feature/doris-28228

Conversation

@OIiveirra

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: N/A (DORIS-28228)

Related PR: N/A

Problem Summary:

Add Iceberg V3 GEOMETRY and GEOGRAPHY support for external tables. The change adds bidirectional Iceberg schema mapping with CRS and Geography algorithm preservation, Arrow/Parquet spatial annotations, raw WKB read/write handling, and spatial function support.

Scope is limited to Iceberg external tables. Doris internal tables reject these types. Paimon, Lance, GeoArrow, spatial indexes, bounds pruning, and predicate pushdown are intentionally excluded.

For Geography, non-default CRS/algorithm values are preserved for read and write. Spatial measurement and relationship functions currently require GEOGRAPHY(OGC:CRS84, spherical) and return a stable NotSupported error for other combinations to avoid incorrect calculations.

Release note

Support Iceberg V3 GEOMETRY and GEOGRAPHY external-table schema mapping and raw-WKB Parquet read/write.

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
      • FE targeted tests: 15 passed (IcebergUtilsTest, IcebergSpatialWriteAnalyzerTest, SpatialConstructorFunctionTest, and ColumnDefinitionTest).
      • BE spatial/Iceberg production and test objects compiled successfully after rebase.
      • Iceberg spatial regression Groovy script compiled successfully.
      • The full external-table regression requires Iceberg REST, MinIO, and Spark services and was not run locally.
      • A clean FE rebuild on the latest upstream baseline is currently blocked before this PR code is compiled by upstream javax.annotation.Generated and Nereids pattern-generator classpath issues.
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
  • Behavior changed:

    • No.
    • Yes. Iceberg external tables can read and write Iceberg V3 GEOMETRY/GEOGRAPHY values as raw WKB; unsupported WKB metadata and unsupported Geography computation semantics fail explicitly.
  • Does this need documentation?

    • No.
    • Yes. A website documentation update will follow once the multi-algorithm Geography computation scope is finalized.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

@OIiveirra
OIiveirra requested a review from yiguolei as a code owner September 13, 2026 05:38
Copilot AI lite review requested due to automatic review settings September 13, 2026 05:38
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved moderate and critical findings affect metadata serialization, spatial I/O, function binding, validation, and test compliance.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds Iceberg V3 GEOMETRY and GEOGRAPHY support for external tables, including CRS/algorithm metadata, raw-WKB Parquet I/O, and spatial-function integration.

Changes:

  • Adds FE/BE spatial types and Thrift/protobuf metadata.
  • Adds Iceberg schema conversion, Parquet annotations, WKB handling, and write validation.
  • Adds spatial functions, tests, and regression coverage.
File summaries
File Summary
regression-test/suites/external_table_p0/iceberg/test_iceberg_spatial_v3.groovy Adds Iceberg V3 spatial regression coverage.
gensrc/thrift/Types.thrift Adds spatial primitive metadata.
gensrc/proto/types.proto Adds spatial protobuf metadata fields.
fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/commands/info/ColumnDefinitionTest.java Tests internal-table spatial rejection.
fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/scalar/SpatialConstructorFunctionTest.java Tests spatial constructors. Critical: uses disallowed JUnit 4 imports; switch to JUnit 5.
fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergUtilsTest.java Tests Iceberg spatial mapping and write rules.
fe/fe-core/src/test/java/org/apache/doris/datasource/iceberg/IcebergSpatialWriteAnalyzerTest.java Tests spatial write validation.
fe/fe-core/src/main/java/org/apache/doris/nereids/types/GeometryType.java Adds parameterized geometry type.
fe/fe-core/src/main/java/org/apache/doris/nereids/types/GeographyType.java Adds parameterized geography type.
fe/fe-core/src/main/java/org/apache/doris/nereids/types/DataType.java Integrates spatial Nereids types.
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/ColumnDefinition.java Rejects spatial internal-table columns. Moderate: nested spatial types can bypass this check.
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/visitor/ScalarFunctionVisitor.java Adds spatial function visitors.
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeomFromWKB.java Adds geometry WKB construction.
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeometryFromWKB.java Adds geometry WKB construction alias.
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StGeogFromWKB.java Adds geography WKB construction.
fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StAstext.java Adds spatial-aware text conversion. Moderate: other supported spatial functions still lack spatial-aware signatures.
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindSink.java Integrates spatial INSERT validation.
fe/fe-core/src/main/java/org/apache/doris/nereids/rules/analysis/BindExpression.java Integrates spatial MERGE validation.
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergUtils.java Maps Iceberg spatial schemas and validates writes. Moderate: nested spatial schemas pass analysis but fail in the Parquet writer.
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/IcebergSpatialWriteAnalyzer.java Validates spatial write compatibility. Moderate: untyped NULL is rejected before sink coercion.
fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/DorisTypeToIcebergType.java Converts Doris spatial types to Iceberg types. Moderate: CREATE/ALTER paths lack the required V3/Parquet guard.
fe/fe-core/src/main/java/org/apache/doris/catalog/BuiltinScalarFunctions.java Registers spatial functions.
fe/fe-core/src/main/java/org/apache/doris/analysis/ColumnDef.java Rejects spatial legacy internal-table columns. Moderate: nested spatial types can bypass this check.
fe/fe-common/src/test/java/org/apache/doris/catalog/SpatialTypeTest.java Tests catalog spatial types.
fe/fe-common/src/main/java/org/apache/doris/catalog/Type.java Handles spatial type metadata.
fe/fe-common/src/main/java/org/apache/doris/catalog/ScalarType.java Preserves CRS and geography algorithms.
fe/fe-common/src/main/java/org/apache/doris/catalog/PrimitiveType.java Registers spatial primitive types.
be/test/format/transformer/vparquet_transformer_test.cpp Tests spatial Parquet writing.
be/test/format/table/iceberg/arrow_schema_util_test.cpp Tests Iceberg Arrow schema mapping.
be/test/exprs/function/function_geo_test.cpp Tests spatial functions. Moderate: both Geography test paths omit the required CRS and algorithm.
be/test/core/data_type/storage_field_type_test.cpp Tests storage field type mappings.
be/test/core/data_type/data_type_spatial_test.cpp Tests backend spatial type behavior.
be/src/format/transformer/vparquet_transformer.cpp Writes spatial Parquet annotations.
be/src/format/table/iceberg/types.h Defines Iceberg spatial types.
be/src/format/table/iceberg/types.cpp Implements Iceberg spatial type handling.
be/src/format/table/iceberg/arrow_schema_util.h Declares Arrow schema utilities.
be/src/format/table/iceberg/arrow_schema_util.cpp Adds Iceberg spatial Arrow annotations. Moderate: the read path lacks matching spatial schema conversion.
be/src/exprs/function/geo/wkb_parse.h Declares WKB parsing helpers.
be/src/exprs/function/geo/wkb_parse.cpp Implements raw-WKB parsing.
be/src/exprs/function/geo/geo_types.h Defines geometry types.
be/src/exprs/function/geo/geo_types.cpp Implements geometry types.
be/src/exprs/function/geo/geo_common.cpp Provides shared spatial functionality.
be/src/exprs/function/geo/functions_geo.h Declares spatial functions.
be/src/exprs/function/geo/functions_geo.cpp Adds WKB parsing and spatial validation. Moderate: Geography construction can omit its required default algorithm; 0x-prefixed hex is mishandled; spatial function signatures are not reachable from many Nereids calls.
be/src/core/data_type/storage_field_type.cpp Adds spatial storage mappings.
be/src/core/data_type/primitive_type.cpp Registers backend primitive mappings.
be/src/core/data_type/define_primitive_type.h Defines primitive type registration.
be/src/core/data_type/data_type_spatial.h Declares backend spatial types.
be/src/core/data_type/data_type_spatial.cpp Implements spatial types and serialization. Moderate: protobuf serialization omits CRS and Geography algorithm metadata.
be/src/core/data_type/data_type_factory.cpp Constructs backend data types. Moderate: protobuf deserialization ignores serialized spatial metadata.
be/src/core/data_type_serde/data_type_varbinary_serde.cpp Supports spatial-related varbinary serialization.
be/src/core/column/column_spatial.h Declares raw-WKB spatial columns.
be/src/core/column/column_spatial.cpp Implements raw-WKB spatial columns.
Review details

Suppressed comments (9)

be/src/core/data_type/data_type_factory.cpp:503

  • Even if the protobuf fields are populated, the protobuf DataTypeFactory path falls through to create_data_type(primitive_type, ...), which reaches these default constructors and ignores PScalarType.spatial_*. As a result, non-default CRS/algorithm values cannot survive protobuf deserialization. Add a spatial branch in the protobuf overload that constructs DataTypeSpatial from the serialized fields, using defaults only for genuinely missing legacy fields.
    case TYPE_GEOMETRY:
        nested = std::make_shared<DataTypeSpatial>(TYPE_GEOMETRY);
        break;
    case TYPE_GEOGRAPHY:
        nested = std::make_shared<DataTypeSpatial>(TYPE_GEOGRAPHY, "OGC:CRS84", "spherical");

be/src/exprs/function/geo/functions_geo.cpp:790

  • The decoder below explicitly accepts 0x-prefixed hex, but the preceding GeoShape::from_wkb call parses the original text and only strips the \\x form. Consequently ST_GEOMFROMWKB('0x...') and ST_GEOGFROMWKB('0x...') are marked NULL before this decoder is reached. Decode the hex first and validate the resulting bytes with from_wkb_bytes.
            auto value = geo->get_data_at(row);
            std::unique_ptr<GeoShape> shape = GeoShape::from_wkb(value.data, value.size, status);
            if (shape == nullptr || status != GEO_PARSE_OK) {
                null_map_data[row] = 1;
                res->insert_default();
                continue;
            }
            if (!decode_wkb_hex(value, &wkb)) {

be/src/exprs/function/geo/functions_geo.cpp:80

  • The new raw-WKB decoder is not reachable for most Nereids calls with the new spatial types. StLength, StAsBinary, StGeometryType, StX/StY, and the measurement/relationship functions still advertise only Varchar/String signatures and, unlike StAstext, have no spatial-aware searchSignature; a GEOGRAPHY(...) argument therefore fails FE signature resolution before this helper and validate_geography_semantics can run. Add spatial overloads or equivalent FE signature handling for the functions covered by this backend support.
static std::unique_ptr<GeoShape> decode_geo_shape(StringRef value, const DataTypePtr& type,
                                                  GeoParseStatus* parse_status = nullptr) {
    if (!is_spatial_type(type)) {
        return GeoShape::from_encoded(value.data, value.size);
    }

    GeoParseStatus status;
    auto shape = GeoShape::from_wkb_bytes(value.data, value.size, status);
    if (parse_status != nullptr) {
        *parse_status = status;
    }
    return status == GEO_PARSE_OK ? std::move(shape) : nullptr;

be/src/format/table/iceberg/arrow_schema_util.cpp:95

  • These annotations make the writer emit spatial Parquet metadata, but the Iceberg read path still has no matching support: V1 FieldDescriptor::convert_to_doris_type falls back from GEOMETRY/GEOGRAPHY BYTE_ARRAY fields to VARBINARY/STRING, and PhysicalToLogicalConverter has no conversion into DataTypeSpatial. Consequently the regression's read-back after insert cannot materialize the WKB into a spatial column. Add the Parquet schema/converter mapping for both annotations before relying on this write-only Arrow mapping.
    case iceberg::TypeID::GEOMETRY:
        metadata[ICEBERG_BINARY_TYPE] = GEOMETRY_BINARY_TYPE_VALUE;
        arrow_type = arrow::binary();
        break;

be/test/exprs/function/function_geo_test.cpp:181

  • This second test path creates a Geography result type with an empty algorithm as well. It violates the same DataTypeSpatial invariant and can fail under DCHECK-enabled builds before the unsupported-WKB behavior is tested; provide OGC:CRS84 and spherical for the Geography branch.
            auto result_type = make_nullable(std::make_shared<DataTypeSpatial>(
                    function_name == "st_geomfromwkb" ? TYPE_GEOMETRY : TYPE_GEOGRAPHY));

fe/fe-core/src/main/java/org/apache/doris/analysis/ColumnDef.java:431

  • The legacy analyzer has the same root-only check: nested ARRAY<GEOMETRY>/STRUCT<...GEOGRAPHY> types do not have GEOMETRY or GEOGRAPHY as their root primitive and therefore can pass internal-table analysis, even though DataTypeSpatial cannot be stored by Doris internal tables. Apply the recursive spatial rejection used by the Nereids path here as well.
    fe/fe-core/src/main/java/org/apache/doris/datasource/iceberg/DorisTypeToIcebergType.java:144
  • These new mappings are also used by Iceberg CREATE/ALTER schema operations, but those paths only apply the existing VARIANT validation (IcebergMetadataOps.java:399,772) and CREATE defaults to format-version 2 (IcebergMetadataOps.java:397). A spatial column can reach schema creation or alteration without the required v3/Parquet guard, even though the new writer only supports that combination; add the spatial validation before committing the schema.
    fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/StAstext.java:80
  • This special-case signature handling only makes ST_ASTEXT bind a spatial operand. StDistance, StContains, StLength, StAsBinary, StGeometryType, StX, and StY still expose only Varchar/String signatures, so Nereids rejects calls such as ST_DISTANCE(iceberg_geography, iceberg_geography) before the new BE implementations can run. Add corresponding spatial-aware signatures/search handling for the supported functions while retaining the Geography metadata checks.
    fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/commands/info/ColumnDefinition.java:388
  • This check only examines the root catalog primitive, so an internal column such as ARRAY<GEOMETRY> or STRUCT<shape GEOGRAPHY> bypasses the rejection because its root is ARRAY/STRUCT. The nested DataTypeSpatial is later unsupported by storage, so reject spatial types recursively here (or reject nested spatial schemas explicitly) to preserve the stated internal-table restriction.
  • Files reviewed: 53/53 changed files
  • Comments generated: 6
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +26 to +27
import org.junit.Assert;
import org.junit.Test;
Comment on lines +126 to +127
void DataTypeSpatial::to_protobuf(PTypeDesc* /* ptype */, PTypeNode* /* node */,
PScalarType* /* scalar_type */) const {}
Comment on lines +812 to +814
DataTypePtr get_return_type_impl(const DataTypes&) const override {
return make_nullable(std::make_shared<DataTypeSpatial>(Impl::OUTPUT_TYPE));
}
input_column->insert_data("0101000000000000000000F03F0000000000000040", 42);
auto input_type = std::make_shared<DataTypeString>();
ColumnsWithTypeAndName arguments {{std::move(input_column), input_type, "wkb"}};
auto result_type = make_nullable(std::make_shared<DataTypeSpatial>(TYPE_GEOGRAPHY));
Comment on lines +94 to +98
static void validateSpatialConversion(Type sourceType, ScalarType targetType, String columnName) {
if (!(sourceType instanceof ScalarType) || !((ScalarType) sourceType).isSpatialType()) {
throw new AnalysisException("Iceberg spatial write cannot convert input column '" + columnName
+ "' from " + sourceType.toSql() + " to " + targetType.toSql());
}
Comment on lines 827 to +833
public static void validateWriteSchema(Table table, List<Column> columns) {
boolean writesVariant = columns.stream().anyMatch(column -> containsVariant(column.getType()));
boolean writesSpatial = columns.stream().anyMatch(column -> containsSpatial(column.getType()));
FileFormat fileFormat = getFileFormat(table);
if (writesVariant) {
if (writesVariant || writesSpatial) {
validateWriteSchema(columns, getFormatVersion(table), fileFormat);
}
@Gabriel39

Copy link
Copy Markdown
Contributor

/review

@Gabriel39 Gabriel39 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Static review of this revision found three correctness/compatibility issues and one performance issue, detailed inline. I recommend addressing these before merging. I have not built this revision or run the BE/FE tests or Iceberg integration suite; the findings are based on tracing the implementation and its callers.

continue;
}
for (size_t row = 0; row < column->size(); ++row) {
if (column->is_null_at(row)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Preserve the null map when validating spatial arguments

GeoFunction uses the default nullable implementation. PreparedFunctionImpl::default_implementation_for_nulls() calls unnest_nullable() before invoking execute_impl(), so this validator receives the nested ColumnSpatial, whose is_null_at() always returns false. A NULL row with the default empty payload is therefore parsed as WKB and returns InvalidArgument before the framework can merge the original null map into the result.

A batch containing both a valid geometry and NULL will fail for SELECT ST_AsText(geom) FROM spatial_source. The same path is reachable through ST_AsText(ST_GeomFromWKB(wkb)) when a nonconstant input column contains both valid and invalid WKB: the constructor produces a NULL with an empty payload for the invalid row, and the outer function rejects it. An all-NULL test can miss this because the framework may short-circuit it.

Please preserve the original null information at the validation boundary and add a mixed NULL/non-NULL function test, while retaining explicit errors for invalid non-NULL spatial values.

if (_iceberg_schema != nullptr) {
ColumnNumbers column_numbers(block.columns());
std::iota(column_numbers.begin(), column_numbers.end(), 0);
RETURN_IF_ERROR(validate_spatial_wkb_inputs(block, column_numbers));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Do not require S2 longitude/latitude semantics for raw GEOMETRY writes

This call validates all spatial payloads through decode_geo_shape() -> GeoShape::from_wkb_bytes() -> WkbParse::readPoint() -> GeoPoint::from_coord() -> to_s2point(). The last step requires abs(x) <= 180 && abs(y) <= 90, regardless of the column's CRS.

For example, a valid GEOMETRY(EPSG:3857) value containing POINT (1000 2000) cannot be copied with INSERT INTO spatial_target SELECT geom FROM spatial_source, even when both schemas have exactly the same CRS: FE validation succeeds, but this writer rejects the payload as invalid WKB. ST_AsText and ST_X encounter the same restriction through the shared validator. The existing non-default CRS write test uses POINT (1 2), which does not expose this.

Please separate WKB structural validation/raw preservation from S2 computation support. Copying a projected geometry must not require its coordinates to be valid longitude/latitude. Iceberg defines GEOMETRY as CRS-parameterized with planar edges: https://iceberg.apache.org/spec/#primitive-types

public static final List<FunctionSignature> SIGNATURES = ImmutableList.of(
FunctionSignature.ret(VarcharType.SYSTEM_DEFAULT).args(VarcharType.SYSTEM_DEFAULT),
FunctionSignature.ret(VarcharType.SYSTEM_DEFAULT).args(StringType.INSTANCE)
FunctionSignature.ret(new GeometryType("OGC:CRS84")).args(VarcharType.SYSTEM_DEFAULT),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Preserve compatibility of existing WKB constructor consumers

Changing the existing ST_GeomFromWKB/ST_GeometryFromWKB result from the legacy VARCHAR spatial encoding to GEOMETRY breaks existing function compositions outside Iceberg. The downstream validate_geography_semantics() now explicitly rejects GEOMETRY for ST_Distance, ST_Length, ST_Contains, and other measurement/relationship functions.

For example, this previously valid expression should return zero, but now reaches the NotSupported error:

SELECT ST_Distance(
    ST_GeomFromWKB('0101000000000000000000F03F0000000000000040'),
    ST_GeomFromWKB('0101000000000000000000F03F0000000000000040'));

The new tests verify that GEOMETRY is rejected, but do not cover compatibility of these pre-existing SQL functions. Please retain the existing constructor contract via a separate typed constructor, or provide an explicit compatibility path for existing consumers.

row);
}
GeoParseStatus parse_status;
if (decode_geo_shape(value, type, &parse_status) == nullptr) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Reuse decoded shapes and validate constant arguments once

The pre-validation pass fully parses every WKB value and immediately destroys the resulting GeoShape; implementations such as StAsText and StDistance then decode the same payload again. This includes stream-buffer copies, coordinate parsing, and S2 object construction, which is expensive for large polygons.

It also defeats the constant-argument optimization in StDistance: for ST_Distance(geog_column, constant_geography), a ColumnConst still reports the logical batch size, so this loop decodes the same constant WKB N times before StDistance reaches its decode-once branch.

Please combine validation with decoding and reuse the result, handling constant arguments once per execution rather than once per logical row. No benchmark was run for this review, so this finding concerns the directly observable redundant work rather than a measured slowdown.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. We agree that the current validation path can decode the same WKB more than once, and that a ColumnConst may be validated once per logical row. This does not affect correctness, but eliminating it cleanly requires sharing decoded shapes across the validation and execution paths and handling constant-column semantics in the function framework. To keep this PR focused on the correctness and compatibility of Iceberg V3 spatial read/write support, we will track that as a follow-up performance optimization, with benchmarks and a focused design for cache lifetime and invalid-input behavior.

@github-actions

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: Review step was failure (possibly timeout or cancelled)
Workflow run: https://github.com/apache/doris/actions/runs/34796671339

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@OIiveirra

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39 Gabriel39 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review of the latest revision: the legacy result-type restoration and the Geometry structural validator address parts of the earlier findings, but three correctness/compatibility issues remain, detailed inline. Please address these before merging. I have not run the complete BE/FE suites or Iceberg regression; the only runtime check in this review was an isolated probe of the original WKB validator source. I will add a separate comment with the concrete test-coverage gaps.

String get_name() const override { return name; }
size_t get_number_of_arguments() const override { return Impl::NUM_ARGS; }
bool is_variadic() const override { return false; }
bool use_default_implementation_for_nulls() const override { return false; }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Keep NULL propagation for numeric GeoFunction implementations

Disabling the default nullable implementation for every GeoFunction introduces incorrect results in existing numeric functions. StPoint::execute() uses ColumnView<TYPE_DOUBLE>::value_at() without checking is_null_at(). ColumnView can unwrap a nullable column, but value_at() only reads the underlying payload; it does not propagate NULL. The result null map starts at zero, and the framework no longer merges the input null maps after execution.

For a nonconstant nullable DOUBLE column x, a NULL row with its default zero payload makes SELECT ST_AsText(ST_Point(x, 5)) FROM coordinates produce POINT (0 5) instead of NULL. ST_Distance_Sphere, ST_Angle_Sphere, and ST_Circle use the same unchecked numeric access pattern.

Please restrict the custom nullable handling to the functions that need it, or implement explicit null checks and propagation in every affected implementation. Run the existing VGeoFunctionsTest.function_geo_st_point_test, which already expects NULL for {Null(), 5} and {5, Null()}, and cover nonconstant mixed-null batches and constant/nullable combinations. A SQL NULL literal alone may be optimized away by FE and miss this regression.

for (int row = 0; row < size; ++row) {
auto shape_value = input->get_data_at(row);
shape = GeoShape::from_encoded(shape_value.data, shape_value.size);
shape = decode_geo_shape(shape_value, input_type);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Preserve projected coordinates in accessors as well as the write validator

The new structural validator accepts projected GEOMETRY values, but the accessor still calls decode_geo_shape() -> GeoShape::from_wkb_bytes() -> WkbParse::readPoint() -> GeoPoint::from_coord() -> to_s2point(). The final step still enforces longitude/latitude limits without consulting the CRS.

Consequently, a non-NULL GEOMETRY(EPSG:3857) containing POINT (1000 2000) can now pass validation and be copied to Parquet, but ST_AsText(geom) returns NULL here. ST_X, ST_Y, ST_GeometryType, and ST_AsBinary share the same decoding restriction. This changes the previous explicit validation error into a silent NULL for valid projected data.

Please use coordinate-preserving WKB handling for accessors and exports; in particular, exporting the existing raw WKB should not depend on constructing an S2 shape. Add a Doris query test that verifies the non-NULL projected value's text, coordinates, type, and binary export after reading it back. The current Arrow writer round-trip test does not exercise these functions.

I independently compiled the original WKB validator source from this revision and verified that it accepts POINT (1000 2000). The downstream NULL result follows from the call chain above; I have not run a complete Doris SQL reproduction.

Comment thread be/src/exprs/function/geo/wkb_parse.cpp Outdated
constexpr uint32_t ewkb_m_flag = 0x40000000;
constexpr uint32_t ewkb_srid_flag = 0x20000000;
constexpr uint32_t ewkb_metadata_flags = ewkb_z_flag | ewkb_m_flag | ewkb_srid_flag;
if ((typeInt & ewkb_metadata_flags) != 0 || (typeInt >= 1000 && typeInt < 4000)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Retain SRID-bearing EWKB support in the legacy constructors

Restoring the legacy constructors' VARCHAR result does not restore their full input compatibility. LegacyStGeoFromWkb still calls GeoShape::from_wkb() -> WkbParse::parse_wkb() -> this shared readGeometry(), which now rejects the SRID flag unconditionally. Previously, this path consumed the SRID and parsed the coordinates.

There is already a regression for this in regression-test/suites/query_p0/sql_functions/spatial_functions/test_gis_function.groovy:460:

SELECT ST_AsText(ST_GeometryFromWKB(
    '01010000208A11000068270210774C5D40B8DECA334C3B4240'));

Its checked-in expected result is POINT (117.194767000297 36.46326301008). The fixture's type is 0x20000001 with SRID 4490, so this branch rejects it before reading the coordinates and the legacy constructor returns NULL.

Please separate the typed Iceberg WKB restrictions from legacy EWKB parsing, and run the existing SQL regression. Add a focused BE test proving that both legacy constructor aliases still accept this input while the new typed constructor follows its explicitly documented SRID policy.

@Gabriel39

Copy link
Copy Markdown
Contributor

The current tests are not sufficient to establish the feature's correctness yet. There is useful coverage for type/metadata round trips, schema rejection, malformed WKB, and Parquet annotations, but the integration suite inserts only one non-NULL point using the default CRS. The PR description also reports object/script compilation rather than execution of the BE spatial tests and the Iceberg regression.

Please add or extend the following coverage and provide execution results for the final revision:

  1. NULL propagation and column wrappers. Exercise ST_Point, ST_Distance_Sphere, ST_Angle_Sphere, and ST_Circle with nonconstant nullable numeric columns, mixed NULL/non-NULL rows, all-NULL batches, and constant arguments on either side. For spatial accessors, cover both GEOMETRY and GEOGRAPHY with nullable and constant inputs. Assert the output null map as well as values. Reuse and run existing numeric-function NULL tests; the new ST_AsText-only NULL test cannot detect the global GeoFunction regression.

  2. Legacy API compatibility and actual function results. Run the existing spatial SQL suite, including its EWKB/SRID fixture, and retain tests for legacy WKB constructors composed with distance/relationship functions. Test the typed constructor's separate metadata-rejection policy. Strengthen function_geo_st_distance_accepts_supported_geography: it currently checks only Status::OK, so an incorrect value or a NULL result would still pass. Assert zero and a known nonzero spherical distance with an appropriate tolerance, and add true/false relationship results and a nonzero length case for supported Geography inputs.

  3. Projected coordinates through Doris accessors. Read a non-NULL GEOMETRY(EPSG:3857) containing POINT (1000 2000) and assert ST_AsText, ST_X, ST_Y, ST_GeometryType, and ST_AsBinary results. Cover this after an INSERT ... SELECT round trip. The writer test now preserves this point through Arrow, which is useful, but does not test Doris decoding/accessors and therefore misses silent NULL results.

  4. Iceberg end-to-end read/write and analyzer integration. Extend the one-row smoke test with supported lines/polygons, mixed NULL rows, INSERT ... SELECT, and explicit NULL insertion into optional spatial columns. Read spatial Parquet data through both reader implementations changed by this PR, including dictionary/plain encoding and more than one batch. Verify raw WKB and CRS/algorithm preservation for representative non-default metadata. Add actual SQL analysis tests for spatial kind/CRS/algorithm mismatch and NULL handling in INSERT and MERGE; direct calls to IcebergSpatialWriteAnalyzer do not prove that sink binding and coercion invoke these checks correctly.

  5. New column serialization and ownership paths. DataTypeSpatialTest currently serializes one nonconstant Geometry value. Add multirow, nullable, empty, and constant-column round trips for both spatial kinds, checking exact payloads and logical row counts. Exercise the in-place filter/COW path and mutation of a copied column to verify that the original payloads remain intact. These tests cover the new ColumnSpatial/serialization implementation independently of the SQL smoke test.

Please use the normal repository test runners and include results from the affected existing BE geo suite, the targeted FE tests, and the expanded Iceberg regression. For deterministic SQL results, use the repository's query/golden-output mechanism and generate the expected output via the runner; use test { sql ...; exception ... } for expected errors. If an environment prevents a suite from running, identify the unexecuted suite and the remaining gap explicitly. Compiling test sources alone does not verify their assertions.

@hello-stephen

Copy link
Copy Markdown
Contributor

Cloud UT Coverage Report

Increment line coverage 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 73.21% (1823/2490)
Line Coverage 61.03% (32628/53463)
Region Coverage 48.42% (30749/63503)
Branch Coverage 51.51% (9578/18596)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants