[spark] Add paimon spark4.2 module - #9265
Conversation
|
Let me resolve the conflicts. |
Raises the `spark4` profile baseline from 4.1.2 to 4.2.0, adds a `paimon-spark-4.2` module, and keeps Spark 4.0 and 4.1 working under the new baseline. Same shape as apache#7648, which did this for 4.1. ## Why the bump breaks the older modules `paimon-spark-common` and `paimon-spark4-common` are compiled once, against the newest supported Spark, and the resulting classfiles ship to every 4.x runtime. Raising the baseline therefore changes bytecode that 4.0 and 4.1 have to load, and Spark 4.2 made several source-compatible but binary-incompatible changes: - `CatalogManager` became an interface, so a 4.2-built call site emits `invokeinterface` and dies with `IncompatibleClassChangeError` on 4.0/4.1. - Case classes gained fields (`CatalogStorageFormat`, `AppendData`, `DataSourceV2ScanRelation`), so positional patterns and named-argument `copy` calls no longer compile or link across versions. - `RewriteRowLevelCommand`'s `DELTA_OPERATIONS_WITH_*` constants were renamed, and `V2WriteCommand` gained a `WriteWithSchemaEvolution` supertype. - `DESCRIBE ... PARTITION` moved out of `DescribeRelation` into its own `DescribeTablePartition` plan (SPARK-39660). - SPARK-57058 folded the geo value classes into `BinaryView`: `SpecializedGetters` lost `getGeometry` / `getGeography` in favour of `getBinaryView`, `GeometryVal` / `GeographyVal` were removed, and `STUtils.stAsBinary` / `stSetSrid` were split into `stGeomAsBinary` / `stGeogAsBinary` and `stGeomSetSrid` / `stGeogSetSrid`. ## Four mechanisms, in order of preference 1. **Version-neutral construction.** Match by type with named accessors instead of positional patterns; build placeholders through factory methods (`CatalogStorageFormat.empty`) rather than arity-sensitive constructors. 2. **`SparkShim` methods** where only the arity differs, so each per-version module supplies its own call. 3. **`SparkVersionCompat`** reflective accessors where the *signature* is incompatible. Reflection is immune to the class/interface flip: only invoke opcodes carry that distinction. 4. **Same-FQCN forks** in `paimon-spark-4.0` / `-4.1` where a supertype or a parameter type differs and no accessor can paper over it. Shade writes the module's own classes before the ones it pulls in from `paimon-spark4-common`, so the fork wins. The geospatial support added by apache#9251 needs mechanism 4. `paimon-spark4-common`'s `Spark4ArrayData`, `Spark4InternalRow` and `Spark4Shim` now implement the 4.2 shape (`getBinaryView`, `stGeomAsBinary` / `stGeogAsBinary`, `stGeogSetSrid`), and `paimon-spark-4.1` forks all three to keep the pre-4.2 pair of overrides, which 4.1 still declares abstract. `paimon-spark-4.0` already forked the two data classes for the same reason -- 4.0 has no geo types at all. `paimon-spark-ut-4.0` and `-4.1` recompile the shared test sources against their own baseline; they produce test-jars only and are deliberately left out of publish and release, unlike `paimon-spark-ut`. ## Also fixed here `qualifyIdentifier` has to carry the catalog so Spark 4.2's `SimpleFunctionRegistryBase.normalizeFuncName` sees a 3-part identifier. The same identifier reached the expression builder, which renamed the default output column of an unaliased v1 function call from `db.udf(...)` to `catalog.db.udf(...)` on every version from 3.4 up. The builder name now drops the catalog; the registry key keeps it. The new test asserts the column name -- the existing cases all compare rows with `checkAnswer` and spell out `AS` wherever a name is involved, so none of them could see it. ## Verification `mvn -Pspark4 clean install` over `paimon-spark-common`, `paimon-spark4-common`, the three ut modules and `paimon-spark-4.0` / `-4.1` / `-4.2`: success. `spotless:check` clean on all six touched modules. Targeted suites: `PaimonV1FunctionTest` 13/13 on 4.2, 4.1 and 4.0; `DescribeTableTest` 4/4 on 4.2, 4.1 and 4.0; `SparkVersionCompatTest` 14/14. For the geospatial path, `GeospatialTypeSQLTest` 2/2 and `GeospatialTypeTest` 2/2 on 4.1 (the version that exercises the forked pre-4.2 overrides) and `GeospatialUnsupportedTest` 1/1 on 4.0. The column-name fix was verified in both directions -- reverting it turns the new case red with `ArraySeq("paimon.test.udf_add2(3, 4)") did not equal List("test.udf_add2(3, 4)")`. The full per-module suites have not been run in this branch yet.
91640c1 to
f37fb20
Compare
All three come from Spark 4.2's storage-partitioned-join rework (SPARK-55535). `EnsureRequirements` now wraps a bucketed scan in `GroupPartitionsExec`, which casts `child.outputPartitioning` to `Partitioning with Expression`. Our AQE prep rule runs after it and disabled the bucketing underneath, which degrades that to `UnknownPartitioning`, so the cast threw at execution time. The traversal now stops at such a node and leaves its whole subtree alone: `EnsureRequirements` wraps whatever satisfied the distribution, which can be a join or an aggregate with several scans below it, and disabling only some of them would instead trip `PartitioningCollection`'s equal-`numPartitions` requirement. `DataSourceRDDPartition`'s payload changed from `Seq[InputPartition]` to `Option[InputPartition]`. `PaimonSparkMicroBatchMetadata` reads that field reflectively and rejected the new shape, and the blanket `catch NonFatal` around it reported that as "metadata absent" instead of failing. It now accepts every shape the field has had: a bare `InputPartition` on 3.2, a `Seq` from 3.3 to 4.1, and an `Option` since 4.2. The accessor is also resolved once per RDD rather than once per partition, since a miss costs a thrown `NoSuchMethodException`. `CREATE TABLE LIKE` with a four-part name is parsed by Spark itself on 4.2 and by Paimon's extension parser below it, so the middle parts arrive split differently. The test now asserts the invariant that holds on every version, which is that the parts survive in order, rather than pinning one version's split.
…LE assertions Spark 4.2 (SPARK-55372) makes `SHOW CREATE TABLE` print the collation on every string-ish column even when the column has none, so that replaying the emitted DDL cannot silently pick up a table- or schema-level `DEFAULT COLLATION` instead. A column declared `STRING` now renders as `STRING COLLATE UTF8_BINARY`, and so do VARCHAR, CHAR and the string leaves of ARRAY / MAP / STRUCT. This is not Paimon-specific: Spark's own golden file expects the same output for a parquet table, and Paimon's string columns reach it through the ordinary `StringType` case object, which Spark reads as "no explicit collation". The 31 failing assertions were all comparing against the pre-4.2 rendering. These tests are compiled once and run against every supported Spark version, so they now route `SHOW CREATE TABLE` through a helper that removes the marker rather than branching on the version. Only ` COLLATE UTF8_BINARY` is removed, so a column carrying a real collation such as `STRING COLLATE UTF8_LCASE` still appears and an assertion cannot be fooled into accepting the wrong collation.
|
cc @melin FYI |
|
cc @Zouxxyy to take a review. |
|
Thank you @JingsongLi |
|
cc @Zouxxyy |
JingsongLi
left a comment
There was a problem hiding this comment.
The Spark 4.2 support has concrete user-facing value (#8901), and the compatibility shims/forks address real binary differences in the shared Spark modules. I reviewed the main compatibility paths, old-version test-jar wiring and release inclusion; I did not find an evidence-backed blocking defect in that scope.
The existing Spark 4.x CI run (https://github.com/apache/paimon/actions/runs/32106655843/job/95617365797) actually executes SQL/storage tests across 4.0, 4.1 and 4.2, including reads, writes and schema evolution. Please resolve the current conflicts and rerun that matrix on the resulting head, including dynamic partition overwrite, MERGE, SQL functions and DESCRIBE PARTITION. I did not independently build the full Spark matrix or exhaustively audit every copied class; this is not a merge approval.
Resolve doc conflicts by taking master's restructured Compatibility Matrix and Spark quick-start, and carry the Spark 4.2 entries into the relocated spark/installation.mdx (version list, download tables, build command) and the ecosystem Spark row.
paimon-spark-4.2, paimon-spark-ut-4.0 and paimon-spark-ut-4.1 are new in this PR, so master's 2.2-SNAPSHOT version bump could not reach them and the merge left their paimon-spark parent pinned at 2.1-SNAPSHOT. The reactor then failed to resolve paimon-spark:pom:2.1-SNAPSHOT and could not read these three projects. Align them with the current 2.2-SNAPSHOT. Co-Authored-By: Claude Code <noreply@anthropic.com>
…on strip cannot tell apart
# Conflicts: # .github/workflows/utitcase-spark-4.x.yml
The old utitcase-spark-4.x.yml this branch edited was deleted upstream; the module list now lives in run-java-tests.sh.
createFormatTableBatchWrite took a fourth overwriteDynamic argument that no constructor accepts, and the interface does not declare: the shared builder already folds that mode into the table options before calling the shim. toSparkVariant was never implemented, so the class was abstract.
Tests 4.2 was missing, plus one that every 4.x module was missing: - KeyPropertyCaseResolutionTest, GeospatialTypeSQLTest, GeospatialTypeTest - SparkInternalRowVariantTest in 4.1 and 4.2, with a typed-access case that covers each module's own Spark4InternalRow.getVariant Test effectiveness: - pin the 4.2 geometry/geography dispatch by giving the geometry column a projected CRS; with both columns on OGC:CRS84 the two branches were indistinguishable - fix the 20-byte POINT_WKB fixture in 4.1 and 4.2 (POINT(1 2) needs 21) Build and infrastructure: - paimon-spark-4.2 gets its own hive-site.xml (port 9093) - drop the StreamTest stub from paimon-spark-4.0: paimon-spark-ut-4.0 compiles the shared test sources against 4.0.3, so the 4.1-only symbols it stood in for are never referenced - deploy paimon-spark-ut-4.0 / -4.1 from publish-snapshot-spark4.yml - expect 4.2 in the spark4 CI self-test and make the spark3 half symmetric Comments and docs that no longer matched the code: - the 4.0 forks of Spark4ArrayData / Spark4InternalRow described the 4.1 baseline; the slf4j pin comment claimed all of Spark 4.x needs SLF4J 2.x - geospatial version range in both the Spark and Iceberg data-type pages
…perty
Only scalatest was skipped, so surefire kept the root pom's default-test and
integration-tests executions and ran the shared Java tests a second time in
paimon-spark-ut-4.0 and -ut-4.1. It failed there rather than merely duplicating
work: neither module carries the SLF4J 2.x pin that paimon-spark-4.0 and -4.1
have, so TestLoggerExtension dies with NoClassDefFoundError on
org/slf4j/spi/LoggingEventBuilder and `mvn install` without -DskipTests exits 1.
Both plugins bind their skipTests parameter to ${skipTests}, so a module
property covers them both. maven.test.skip would also skip test compilation and
leave the test-jar these modules exist to produce empty.
|
A rebase will be required after merging #9794. |
# Conflicts: # .github/workflows/publish-snapshot-spark4.yml # paimon-spark/paimon-spark-4.0/pom.xml # paimon-spark/paimon-spark-ut-4.0/pom.xml # pom.xml
|
Can we merge this first, and I'll address any follow-up issues in subsequent fixes? @JingsongLi |
Purpose
Raises the
spark4profile baseline from 4.1.2 to 4.2.0, adds apaimon-spark-4.2module, and keeps Spark 4.0 and 4.1 working under the new baseline. Same shape as #7648, which did this for 4.1. Related to #8901.paimon-spark-commonandpaimon-spark4-commonare compiled once, against the newest supported Spark, and the resulting classfiles ship to every 4.x runtime. Raising the baseline therefore changes bytecode that 4.0 and 4.1 have to load, and Spark 4.2 made several source-compatible but binary-incompatible changes:CatalogManagerbecame an interface (a 4.2-built call site emitsinvokeinterfaceand dies withIncompatibleClassChangeErroron 4.0/4.1); case classes gained fields (CatalogStorageFormat,AppendData,DataSourceV2ScanRelation), so positional patterns and named-argumentcopycalls no longer link across versions;RewriteRowLevelCommand'sDELTA_OPERATIONS_WITH_*constants were renamed;V2WriteCommandgained aWriteWithSchemaEvolutionsupertype; andDESCRIBE ... PARTITIONmoved out ofDescribeRelationinto its ownDescribeTablePartitionplan (SPARK-39660).Four mechanisms are used, in order of preference:
CatalogStorageFormat.empty) rather than arity-sensitive constructors.SparkShimmethods where only the arity differs, so each per-version module supplies its own call.SparkVersionCompatreflective accessors where the signature is incompatible. Reflection is immune to the class/interface flip, since only invoke opcodes carry that distinction.paimon-spark-4.0/-4.1where a supertype or a parameter type differs and no accessor can paper over it. Shade writes the module's own classes first, so the fork wins.paimon-spark-ut-4.0and-4.1recompile the shared test sources against their own baseline and produce test-jars only. They setskipTestsas a module property rather than as scalatest plugin configuration: surefire inherits the root pom'sdefault-testandintegration-testsexecutions, and both plugins bind that parameter to${skipTests}. Without it,mvn installover the reactor without-DskipTestsruns the shared Java tests a second time there and fails onTestLoggerExtensionwithNoClassDefFoundError: org/slf4j/spi/LoggingEventBuilder, since neither module carries the SLF4J 2.x pin thatpaimon-spark-4.0and-4.1have.publish-snapshot-spark4.ymldeploys them next topaimon-spark-ut, because the published 4.0 and 4.1 poms now declare those test-jars at test scope. The release lane publishes none of the three, which is howpaimon-spark-utwas already handled.That also retires
StreamTestCheckAnswerWithTimeoutStubinpaimon-spark-4.0. Its eight empty classes stood in forStreamTestinner classes that exist only from 4.1 on, which the baseline-compiled test classfiles reached through mix-in forwarders and a 4.0 runtime could not resolve. Compiled against 4.0.3 those references are never emitted: thepaimon-spark-ut-4.0test-jar and the baseline one carry the same 589 classes, 15 of which name those symbols in the baseline build and none in the 4.0 build.One behaviour fix is included because the baseline bump caused it.
qualifyIdentifierhas to carry the catalog so Spark 4.2'sSimpleFunctionRegistryBase.normalizeFuncNamesees a 3-part identifier, but the same identifier reached the expression builder, which renamed the default output column of an unaliased v1 function call fromdb.udf(...)tocatalog.db.udf(...)on every version from 3.4 up. The builder name now drops the catalog; the registry key keeps it.On the test side,
paimon-spark-4.2picks up the three suites its siblings already have (KeyPropertyCaseResolutionTest,GeospatialTypeSQLTest,GeospatialTypeTest) and gets its ownhive-site.xml, so its Hive suites bind port 9093 instead of picking up 9083 from the shared ut test-jar.SparkInternalRowVariantTestlived only inpaimon-spark-4.0, even though #9493 addedtoSparkVariantto the 4.1 shim in the same commit; it now runs in all three 4.x modules, with a second case for the typedgetVariantgetter that the genericget(pos, VariantType)path never reaches.Porting those suites turned up two tests that could not fail.
POINT_WKBwas 20 bytes where a little-endian POINT(1 2) needs 21, so the 4.1 fixture had been asserting a round-trip over malformed WKB; 4.2 parses it and rejects it. AndSpark4InternalRow.getBinaryViewdispatches on the Paimon field type, which nothing could observe while both columns usedOGC:CRS84and the same bytes, so the geometry column now uses the projectedEPSG:3857.Tests
All 25 CI lanes passed on 0094db2, including
Java / Spark 4 / Scala 2.13(1h4m) and both Spark 3 lanes. The branch has since been merged with master (9140cb3, which already contains #9794) and CI is re-running.On the merge head, locally on JDK 17:
paimon-spark-4.246 suites / 1302 tests,paimon-spark-4.146 / 1302,paimon-spark-4.044 / 1219 with 22 version-gated cancellations. No failures, no aborted suites.Locally on JDK 17 unless noted, over a chain rebuilt with
clean install:paimon-spark-4.246 suites / 1302 tests,paimon-spark-4.146 / 1302,paimon-spark-4.044 / 1219 with 22 version-gated cancellations. No failures.paimon-spark-ut124 suites / 955 tests plus 74 JUnit tests. Its only failures are the 11 inLuminaVectorIndexTest, which cannot run on this machine at all:lumina-jnishipslinux/amd64natives only.mvn -Papache-release,spark4,flink1 clean installoverpaimon-spark-ut-4.0and-ut-4.1, matching what the snapshot workflow runs: success.mvn -Pspark3 clean compileoverpaimon-spark-common+paimon-spark3-commonon JDK 11: success.spotless:checkclean on the eight modules thespark4profile builds here, and onpaimon-spark3-common+paimon-spark-commonunder-Pflink1,spark3,scala-2.12.mvn -pl paimon-spark-ut-4.0,paimon-spark-ut-4.1 -Pflink1,spark4 installwithout-DskipTests: success, sixTests are skipped.lines, 588 test classes still compiled in each. Before the property was added the same command exited 1 with theNoClassDefFoundErrorabove, which is how that defect was found.python3 -m unittest discover -s tools/ci: 28 tests, including the spark4 module list, which now also fails when a version is added rather than only when one is dropped.Three of the fixes were checked by breaking the code they cover:
getBinaryViewfailsGeospatialTypeTestwith[ST_INVALID_CRS_VALUE] ... 'EPSG:3857', thrown fromGeographyType$.toSridon the firstgetBinaryView(0);value()andmetadata()intoSparkVariantfails the generic case, and doing the same inSpark4InternalRow.getVariantfails only the typed case, in both the 4.1 fork andpaimon-spark4-common;PaimonV1FunctionTestBasecase red withArraySeq("paimon.test.udf_add2(3, 4)") did not equal List("test.udf_add2(3, 4)").That last case asserts the column name rather than the row values, which is why the existing cases could not catch the regression: they compare rows with
checkAnswerand spell outASwherever a name is involved.The 4.0 stub removal was verified after clearing
target/test-classes, since the compiled stubs survive a source deletion and a green run over stale output proves nothing.Three of the fixes were checked by breaking the code they cover:
getBinaryViewfailsGeospatialTypeTestwith[ST_INVALID_CRS_VALUE] ... 'EPSG:3857', thrown fromGeographyType$.toSridon the firstgetBinaryView(0);value()andmetadata()intoSparkVariantfails the generic case, and doing the same inSpark4InternalRow.getVariantfails only the typed case, in both the 4.1 fork andpaimon-spark4-common;PaimonV1FunctionTestBasecase red withArraySeq("paimon.test.udf_add2(3, 4)") did not equal List("test.udf_add2(3, 4)").That last case asserts the column name rather than the row values, which is why the existing cases could not catch the regression: they compare rows with
checkAnswerand spell outASwherever a name is involved.The 4.0 stub removal was verified after clearing
target/test-classes, since the compiled stubs survive a source deletion and a green run over stale output proves nothing.