Skip to content

[refactor](catalog) P5 paimon: migrate to catalog SPI + cutover (#64446)#64446

Merged
morningman merged 121 commits into
apache:branch-catalog-spifrom
morningman:catalog-spi-07-paimon
Jun 20, 2026
Merged

[refactor](catalog) P5 paimon: migrate to catalog SPI + cutover (#64446)#64446
morningman merged 121 commits into
apache:branch-catalog-spifrom
morningman:catalog-spi-07-paimon

Conversation

@morningman

@morningman morningman commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Migrates the legacy in-tree Paimon catalog (fe-core datasource/paimon,
metacache/paimon, property/metastore/*Paimon* and their reverse
references) onto the catalog SPI as a self-contained fe-connector-paimon
plugin, following the maxcompute full-adopter + cutover template. paimon
is added to SPI_READY_TYPES, so a Paimon catalog now deserializes to
PluginDrivenExternalCatalog and all five functional areas — normal-table
read, system-table read, DDL, MTMV/MVCC/time-travel (procedures are a
doc-only no-op: fe-core has none) — flow through the generic PluginDriven*
bridge with no Paimon-specific branch in fe-core.

Paimon is the first lakehouse full-adopter to exercise the MVCC / sys-table /
MTMV SPI surfaces, which become the reusable template for the upcoming
iceberg/hudi migrations, so the cutover is held to byte-parity with the
legacy path. The legacy datasource/paimon/* classes are left in place (now
dead on the cutover path) and removed in a follow-up, mirroring the
maxcompute cutover -> P4 removal.

1. Generic connector-SPI surface (fe-connector-api / -spi, DefaultConnectorContext)

Source-agnostic seams so any connector can express these capabilities; every
new method is a default no-op/identity, so hive/iceberg/jdbc/maxcompute/
trino/es are byte-for-byte unaffected.

  • MVCC/time-travel: resolveTimeTravel(session, handle, ConnectorTimeTravelSpec)
    • applySnapshot(...) (threads a pinned snapshot into the handle before
      planScan); new immutable ConnectorTimeTravelSpec (SNAPSHOT_ID / TIMESTAMP /
      TAG / BRANCH / INCREMENTAL) that fe-core extracts from SQL; ConnectorMvccSnapshot
      carries schemaId for schema-evolution-aware time travel.
  • System tables: listSupportedSysTables / getSysTableHandle; fe-core composes
    the {base}${sys} reference name.
  • Scan: proportional split-weight (getSelfSplitWeight/getTargetSplitSize),
    COUNT(*) pushdown (getPushDownRowCount, 7-arg planScan), native-read
    accounting (isNativeReadRange), getDeleteFiles (VERBOSE merge-on-read),
    ignorePartitionPruneShortCircuit (predicate-driven connectors keep genuine-null
    partitions).
  • Metadata: ConnectorPartitionInfo.fileCount + SUPPORTS_PARTITION_STATS (rich
    SHOW PARTITIONS); ConnectorColumn.withTimeZone (WITH_TIMEZONE DESC marker,
    independent of the timestamp_tz mapping flag); connector cache control
    (invalidateTable/invalidateAll, schemaCacheTtlSecondOverride).
  • ConnectorContext: six engine-side hooks the connector must not implement itself
    (loadHiveConfResources, vendStorageCredentials, getBackendStorageProperties,
    normalizeStorageUri x2, getStorageProperties), bound in DefaultConnectorContext
    over fe-core's single-source-of-truth credential/URI/HiveConf utilities (no
    re-ported logic that could drift).

2. Pluggable metastore SPI (NEW fe-connector-metastore-api / -spi)

The metastore-side counterpart of the fe-filesystem StorageProperties SPI: a
Paimon catalog resolves its backend (hive/dlf/rest/jdbc/filesystem) by
ServiceLoader dispatch on paimon.catalog.type instead of a fe-core switch.

  • fe-connector-metastore-api: hadoop/SDK-free MetaStoreProperties contracts
    (HMS/DLF/REST/JDBC/FileSystem) carrying only Map/scalar facts.
  • fe-connector-metastore-spi: MetaStoreProvider<P> + first-hit MetaStoreProviders
    dispatcher + five impls, registered via META-INF/services. HmsMetaStorePropertiesImpl
    reproduces the legacy buildHmsHiveConf key set and parity-critical ordering
    (storage overlay before the kerberos block; username/sasl last). Adding a backend =
    one provider class + one services line (no central enum/switch).
  • ServiceLoader is loaded with the SPI interface's own (plugin) classloader, NOT the
    thread-context CL: at CREATE CATALOG the static init first fires on an FE worker
    thread whose TCCL is the FE app loader, so a 1-arg load would cache an empty
    provider list process-wide.
  • fe-core bridges add initExecutionAuthenticator(List<StorageProperties>) so
    HDFS-Kerberos doAs is wired on the plugin path (legacy initializeCatalog is dead).

3. The Paimon connector (fe-connector-paimon)

Full read + DDL + partitions + statistics + system tables + MVCC/time-travel across
all five metastore flavors, planning native (ORC/Parquet) vs JNI reads.

  • PaimonCatalogFactory (pure flavor switch: Options + Hadoop Configuration + HiveConf,
    classloader-pinned for child-first loading), PaimonCatalogOps (injection seam over
    the remote Catalog for offline-testable metadata), PaimonConnectorMetadata (~9 -> 28
    methods: schema latest/at-snapshot, sys tables, MVCC/time-travel, DDL, partitions,
    statistics, table descriptor).
  • PaimonScanPlanProvider (~6 -> 30 methods): predicate/projection pushdown,
    native-vs-JNI routing, large-file sub-splitting with deletion vectors, COUNT(*)
    collapse, schema-evolution field-id dictionary, $ro unwrap to base FileStoreTable.
  • New: PaimonSchemaBuilder (CREATE TABLE), PaimonIncrementalScanParams (@incr),
    PaimonTableResolver (sys/branch-aware handle -> Table), PaimonLatestSnapshotCache
    • PaimonSchemaAtMemo (per-catalog caches on the long-lived connector).
  • PaimonTypeMapping gains the write direction (Doris -> Paimon) for CREATE TABLE.

4. fe-core PluginDriven host + cutover

  • NEW PluginDrivenMvccExternalTable (generic MvccTable + MTMV host: the runtime class
    for paimon tables), PluginDrivenMvccSnapshot, PluginDrivenSysExternalTable +
    systable/PluginDrivenSysTable. Behavior is selected by ConnectorCapability
    (SUPPORTS_MVCC_SNAPSHOT, SUPPORTS_PARTITION_STATS) — no paimon branch in fe-core.
  • PluginDrivenScanNode: MVCC pin threaded at every handle-consumption site, sys-table
    time-travel guard, COUNT pushdown, connector-agnostic EXPLAIN re-emission.
  • Cutover: CatalogFactory adds paimon to SPI_READY_TYPES and drops the built-in
    case; GsonUtils removes the 7 built-in Paimon* subtype registrations and adds
    replay compat so persisted PaimonExternalCatalog (all 5 flavors) / ...Database
    -> PluginDriven* and PaimonExternalTable -> PluginDrivenMvccExternalTable on
    deserialization (existing clusters upgrade without losing catalogs).
  • Nereids/DDL: PhysicalPlanTranslator drops the PaimonScanNode branch;
    UserAuthentication, CreateTableInfo, ShowPartitionsCommand generalized;
    Env.getDdlStmt renders LOCATION/PROPERTIES for SHOW CREATE (paimon engine, no
    credential leak).

5. Supporting infrastructure

  • fe-filesystem: NEW HdfsFileSystemProperties / HdfsConfigFileLoader (typed HDFS;
    defaults-laden BE map vs defaults-free FE Hadoop-config map so it cannot clobber a
    co-bound object store's fs.s3a.* during a multi-backend merge); S3 MinIO support +
    legacy tuning defaults; OSS/OBS/COS ANONYMOUS provider-type; new
    FileSystemFactory.bindAllStorageProperties / FileSystemPluginManager.bindAll.
  • NEW fe-kerberos leaf module: dependency-free AuthType / KerberosAuthSpec facts
    shared across modules without pulling in Hadoop.
  • Packaging: NEW fe-connector-paimon-hive-shade relocates org.apache.thrift for
    HMS 2.3.7; the paimon plugin-zip excludes fe-thrift/libthrift (provided parent-first)
    to avoid a split TBase across loaders; self-contained bundle (hadoop-aws, AWS-SDK,
    hadoop-hdfs-client). paimon.version (1.3.1) pinned as the single property across
    FE connector / BE paimon-scanner / preload-extensions for FE<->BE Table/Split serde.
  • BE PaimonJniScanner.getPredicates() null-predicate backstop (no-filter scan; pairs
    with the FE producer now always emitting an empty predicate list).

6. Behavior-preservation fixes vs the legacy path

Parity fixes surfaced by the SPI cutover (each restores legacy semantics):

  • Storage/credentials: canonical s3.*/oss.* keys -> fs.s3a.*/fs.oss.*
    (STORAGE-CREDS); canonical AWS_* BE creds (STATIC-CREDS-BE); per-table vended
    DLF/STS token (REST-VENDED); oss/cos/obs/s3a -> s3:// for data + deletion-vector
    paths (URI-NORMALIZE); external hive.conf.resources hive-site.xml (HMS-CONFRES);
    real doAs on filesystem/jdbc + wrapped HMS read RPCs (KERBEROS-DOAS).
  • Reads/types: paimon columns forced nullable (READ-NOTNULL); VARCHAR(65533) not
    widened to STRING (VARCHAR-BOUNDARY); dotted enable.mapping.varbinary /
    .timestamp_tz keys (MAPPING-FLAG-KEYS); per-type partition-value rendering
    (NATIVE-PARTVAL); genuine \N not coerced to NULL (PARTITION-NULL-SENTINEL);
    field-id history dict for rename-safe native reads (SCHEMA-EVOLUTION); paimon-native
    split encode under enable_paimon_cpp_reader (CPP-READER).
  • Planning/perf: restore the force_jni_scanner escape hatch (FORCE-JNI-SCANNER);
    intra-file native sub-splitting (NATIVE-SUBSPLIT); precomputed COUNT(*) row count
    (COUNT-PUSHDOWN); table row count for CBO / SHOW TABLE STATUS (TABLE-STATS).
  • Time travel / DDL: FOR TIME AS OF under CST/PST session zones (TZ-ALIAS); resolved
    • validated driver_url for jdbc catalogs (JDBC-DRIVER-URL); reject a local-only
      name conflict on CREATE TABLE (CREATE-TABLE-LOCAL-CONFLICT).

7. Doc-sync

plan-doc P5 design / task-list / HANDOFF / deviation tracking notes.

Tests & verification

  • ~70 new FE unit suites: paimon connector (offline recording fakes:
    RecordingPaimonCatalogOps / FakePaimonTable), fe-core PluginDriven* MVCC /
    sys-table / scan-node / split-weight, metastore-spi dispatch + per-backend
    properties, fe-filesystem HDFS/MinIO/anonymous, fe-kerberos.
  • checkstyle 0 violations; connector import-gate passes; FE main+test compile clean.
  • End-to-end paimon behavior is docker-gated (enablePaimonTest=true); the five
    functional areas are covered by the external_table_p0/paimon regression suites,
    green on the latest CI run for this branch.

@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?

@morningman morningman force-pushed the branch-catalog-spi branch from b8d6426 to f09b6df Compare June 12, 2026 14:22
@morningman morningman force-pushed the catalog-spi-07-paimon branch from d6c93da to f7114a2 Compare June 12, 2026 14:23
@morningman morningman force-pushed the branch-catalog-spi branch from f09b6df to e9c5b3e Compare June 16, 2026 23:02
morningman and others added 26 commits June 17, 2026 07:03
本 session 仅调研+设计。14-agent code-grounded recon + cross-cut 对抗复审,
覆盖 paimon 5 功能区(普通读/系统表/procedure/DDL/mtmv)旧框架实现 →
映射新 catalog SPI → 对齐 maxcompute 连接器接口一致性。

新增:
- research/p5-paimon-migration-recon.md: 5 区旧实现 + E1–E10 SPI 状态 +
  跨切面风险 + MC 一致性 11 约定 + 测试基线
- tasks/P5-paimon-migration.md: old→new 映射 + 30 TODO/B0–B9 批 +
  批次依赖图 + 验收标准

用户签字决策:
- D-037 (P5-D1): flavor=单 Catalog + createCatalog flavor switch(MC 一致,
  不建 backend 模块——5 个 backend 模块是空壳)
- D-038 (P5-D2): MTMV/MVCC 桥 P5 内实现(fe-core PaimonPluginDrivenExternalTable),
  翻闸 gated on 它,禁静默读 latest 回归

证伪 3 先验: backend 模块空壳(连接器走单 Catalog stub)/ FE 分发部分已预接
(残留=连接器 listPartitions)/ Base64 非 blocker(BE 有 STD fallback)。
procedure 区=零可迁 doc-only。

doc 同步: connectors/paimon.md(修 3 stale 表述)、decisions-log.md(+D-037/D-038,
36→38)、PROGRESS.md(header/§一/§二/§三/§四/§六/§七)、HANDOFF.md(覆盖,不留折叠历史)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
T01: extract PaimonCatalogOps injection seam (5 read methods, B0 read-only)
over the paimon SDK Catalog; refactor PaimonConnectorMetadata to inject it
(6 call sites migrated, read path byte-for-byte unchanged); build the first
fe-connector-paimon test module (no-mockito recording fake, mirroring MC's
McStructureHelper): 9 metadata UTs pinning the databaseExists try/catch and
the getColumnHandles reload-fallback, FakePaimonTable (fail-loud on non-read
methods), and an env-gated live connectivity smoke.

T02: R-007 paimon.version 3-way pin invariant comment (FE connector + BE
paimon-scanner + preload-extensions already aligned at 1.3.1 via the single
fe/pom.xml property); offline FE->BE serialized-Table round-trip smoke (real
FileSystemCatalog -> connector encode -> BE-mirrored URL-first/STD-fallback
decode, asserts rowType/partition/primary keys); parity-baseline doc
inventorying the 41 existing regression suites as the after-cutover parity
gate plus the real connector-side gaps and the live-e2e hard gate.

Connector module: Tests run: 12, Failures: 0, Errors: 0, Skipped: 1 (the
skip is the env-gated live test); checkstyle 0; import-gate clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Single-Catalog flavor switch on paimon.catalog.type for all five flavors
(filesystem/hms/rest/jdbc/dlf), mirroring the legacy fe-core flavor
properties without importing fe-core/fe-common.

- New PaimonCatalogFactory: pure validate() + buildCatalogOptions()
  (paimon.catalog.type -> paimon `metastore` opt, per-flavor options,
  paimon.* passthrough excl storage prefixes) + buildHadoopConfiguration /
  buildHmsHiveConf / buildDlfHiveConf + requireOssStorageForDlf.
- PaimonConnector: thread ConnectorContext; createCatalog wires all 5
  flavors live (filesystem/jdbc with Hadoop Configuration, rest
  Options-only, hms/dlf with HiveConf), each wrapped in
  context.executeAuthenticated (Kerberos seam). JDBC DriverShim ported with
  driver-url resolution via getEnvironment() (replaces forbidden JdbcResource).
- PaimonConnectorProperties: all flavor key constants (multi-alias String[]).
- PaimonConnectorProvider: validateProperties override -> factory.validate.
- pom: add paimon-hive-connector-3.1 + hadoop-common + hive-common
  (hive-common over hive-catalog-shade to avoid the fastutil conflict).
- 31 new no-mockito unit tests (PaimonCatalogFactoryTest); module 43/0/0/1,
  checkstyle 0, import-gate clean.

hms/dlf live connection is gated on B7 cutover + live-e2e: the Thrift
metastore client is host-provided (not bundled) with a child-first
Configuration/HiveConf cross-loader hazard to verify; jdbc driver_url FE
security allow-list + external hive-site.xml file load are deferred. All
documented in code NOTEs and plan-doc. rest also requires warehouse
(legacy parity).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Connector-side only; no fe-core / fe-connector-api / fe-connector-spi changes.
B2 and B3 were both uncommitted and are entangled in the same files
(PaimonConnectorMetadata, PaimonCatalogOps, PaimonConnector,
RecordingPaimonCatalogOps), so they are committed together.

B2 normal-read (T06-T10):
- T06 PaimonScanPlanProvider transient-Table reload fallback (planScan +
  getScanNodeProperties both guarded)
- T07 PaimonPredicateConverter parity-correct TZ (NTZ keeps UTC, LTZ not
  pushed) + supportsCastPredicatePushdown=false
- T08 listPartitionNames/listPartitions/listPartitionValues (legacy
  display-name parity) + seam listPartitions(Identifier)
- T09 doc-only pure-predicate pruning; T10 cache deferred to B8

B3 DDL metadata (T11-T15):
- T11 PaimonTypeMapping.toPaimonType (Doris->paimon, byte-parity with legacy
  DorisToPaimonTypeVisitor; narrow gap preserved)
- T12 PaimonSchemaBuilder (ConnectorCreateTableRequest -> paimon Schema)
- T13 createTable/dropTable + seam DDL methods + ConnectorContext threaded
  (D7=B: each DDL op wrapped in executeAuthenticated; read path un-wrapped)
- T14 supportsCreateDatabase/createDatabase (HMS-props gate) +
  dropDatabase(force) (enumerate-loop + native cascade)
- T15 offline UTs (no-mockito; WHY+MUTATION)

Verified: fe-connector-paimon Tests run: 96, Failures: 0, Errors: 0,
Skipped: 1 (live); checkstyle 0; connector import-gate 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Port paimon system tables and MVCC snapshots onto the plugin connector SPI.

- T16: greenfield E7 SPI on ConnectorTableOps — listSupportedSysTables +
  getSysTableHandle (default no-ops; MC/jdbc/es/trino unaffected).
- T17: PaimonConnectorMetadata implements E7 — names from
  SystemTableLoader.SYSTEM_TABLES; sys table loaded via the existing
  getTable seam with a 4-arg Identifier(db,table,"main",sysName); sys
  handle carries sysTableName + forceJni (binlog/audit_log); shared
  PaimonTableResolver gives metadata + scan one sys-aware reload rule.
- T18: generic fe-core glue — PluginDrivenExternalTable centralizes handle
  acquisition into resolveConnectorTableHandle and delegates
  getSupportedSysTables to the connector; new PluginDrivenSysExternalTable
  (reports PLUGIN_EXTERNAL_TABLE) + PluginDrivenSysTable reuse the live
  SysTableResolver/NativeSysTable machinery (reusable by future connectors).
- T19: forceJni gate so binlog/audit_log go JNI not native; buildTableDescriptor
  -> HIVE_TABLE (also fixes a latent normal-table SCHEMA_TABLE descriptor gap,
  DV-024); PluginDrivenScanNode fail-loud guard rejects scan-params/time-travel
  on system tables.
- T20: first E5 MVCC consumer — beginQuerySnapshot/getSnapshotAt/getSnapshotById
  (empty table -> -1; sys handle -> empty) + SUPPORTS_MVCC_SNAPSHOT/TIME_TRAVEL
  capabilities. Inert until B5 wires the fe-core MvccTable consumer.

Decisions: D-039 (E7 reuses the live SysTable machinery; RFC §10's
$-suffix-via-getTableHandle design was never implemented and is superseded,
DV-023). Deviations: DV-023, DV-024.

Verification: import-gate 0; connector 124 tests pass (1 live skipped);
fe-core PluginDriven*Test 100 pass; checkstyle 0; no cutover/B5 leakage
(paimon not in SPI_READY_TYPES; PluginDrivenExternalTable still not an MvccTable).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ridge + time-travel + procedure doc no-op

B5a (MTMV/MVCC bridge): source-agnostic PluginDrivenMvccExternalTable (MTMVRelatedTableIf+MTMVBaseTableIf+MvccTable, D-042) wiring the B4-inert E5 snapshot SPI; PluginDrivenMvccSnapshot; list-partitions-at-snapshot.
B5b (time-travel): scan-pin + AS-OF + tag + branch + @incr across connector (ConnectorTimeTravelSpec, PaimonIncrementalScanParams) and fe-core; holistic review fixes RD-1 (partitioned time-travel empty-universe scan-all guard in PluginDrivenScanNode) + RD-2 (@incr lists-latest partitions/schema).
B6/T26: procedure doc no-op — zero migratable code; closed-form reject verified (ExecuteActionFactory:59-62 / CallFunc:42-43).
All inert/gated until B7 cutover (paimon NOT yet in SPI_READY_TYPES). Excludes regression-conf.groovy (secrets) + scratch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eview fixes

Combines all previously-uncommitted P5 paimon work into one commit (per request).

8 fullpath-review fixes (BLOCKERs + key MAJORs) — connector + SPI + fe-core bridge:
- FIX-STORAGE-CREDS: applyStorageConfig translates canonical s3.*/oss.*/AWS_* ->
  fs.s3a./fs.oss. (+DLF region->OSS endpoint)
- FIX-NATIVE-PARTVAL: per-type serializePartitionValue + session TZ (LTZ only);
  binary/varbinary drops the partition map (no [B@hash garbage)
- FIX-TZ-ALIAS: full legacy ZoneId.SHORT_IDS + 4 Doris overrides alias map
  (CST/PST/EST now resolve for FOR TIME AS OF datetime strings)
- FIX-TABLE-STATS: getTableStatistics override + PaimonCatalogOps.rowCount seam
  (normal AND system tables, via the sys-aware resolveTable)
- FIX-CPP-READER: honor enable_paimon_cpp_reader -> native DataSplit.serialize so
  BE's PaimonCppReader can decode the split
- FIX-READ-NOTNULL: mapFields forces read-path columns nullable (legacy parity)
- FIX-HMS-CONFRES: new ConnectorContext.loadHiveConfResources hook + 2-arg
  buildHmsHiveConf file-base merge (external hive-site.xml reaches the metastore)
- FIX-REST-VENDED: new ConnectorContext.vendStorageCredentials hook + scan-props
  vended AWS_* overlay (REST per-table tokens reach BE)

Also carries the previously-uncommitted B7 core cutover + D-045/D-046 restores.

Tests: fe-connector-paimon 213 pass / 0 fail / 1 skip (live-gated); fe-core compiles +
DefaultConnectorContextVendTest 2/0. Each fix's root-cause/patch/UT and impl-time
corrections are in plan-doc/tasks/designs/P5-fix-<id>-design.md.

Excluded from this commit: regression-test/conf/regression-conf.groovy (plaintext Aliyun
keys, pending scrub) and scratch dirs (.audit-scratch/, conf.cmy/, META-INF/, *.bak).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…canonical scheme

Root cause: the paimon connector sent native ORC/Parquet data-file paths and
deletion-vector (DV) paths to BE un-normalized. The paimon SDK emits
warehouse-native schemes (oss://, cos://, obs://, s3a://, or the OSS
bucket.endpoint authority form); BE's scheme-dispatched S3 file factory only
recognizes s3://. On S3-compatible (non-AWS) warehouses this breaks native reads
outright (B-7DF, data file) and silently drops the DV so DELETEd rows reappear
(B-7DV, merge-on-read corruption). Legacy PaimonScanNode normalized both via the
2-arg LocationPath.of; the cutover dropped it. The two paths reach BE via
different mechanisms (data-file through PluginDrivenSplit's single-arg
LocationPath.of -> FileQueryScanNode:568; DV baked into thrift by the connector's
populateRangeParams), so a fe-core-bridge-only fix cannot reach the DV path.

Solution: new ConnectorContext.normalizeStorageUri SPI hook (identity default,
mirroring vendStorageCredentials), implemented in DefaultConnectorContext via the
engine's 2-arg normalizing LocationPath.of with the catalog's static storage map
(threaded via a new lazy supplier + 4-arg ctor; PluginDrivenExternalCatalog wires
it). The connector routes BOTH the data-file and DV paths through it inside the
extracted, unit-testable buildNativeRange. JNI path untouched (carries its own
FileIO). Fail-loud on un-normalizable paths (legacy parity). Static-vs-vended map
scope noted in DV-025 (the pure-vended edge belongs to credential fixes #2/#3).

Tests: fe-core DefaultConnectorContextNormalizeUriTest (oss->s3, s3 idempotent,
null/blank, empty-map fail-loud); connector PaimonScanPlanProviderTest x3 (both
paths normalized + call count, DV-less, no-context raw). paimon module 216/0/0,
fe-core targeted green, checkstyle 0, import-gate clean. Live OSS+DV e2e CI-gated
(not run). SPI RFC section 21 (E13), deviations DV-025.

Also includes the round-2 review report + task list this fix derives from.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mark FIX-URI-NORMALIZE complete (commit 20b19d1) in the task list and update
HANDOFF: #1 summary + verification, next session starts at #2 (reuse the
normalizeStorageUri BE-scan-prop normalization seam), and the standing reminders
(regression-conf.groovy still holds a plaintext key -> path-whitelist only; P2
apache#8/apache#9 need user scope decision first).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…canonical AWS_*

Finding B-9 (BLOCKER, rereview2). The paimon connector copied static
catalog-level storage credentials/config verbatim into the BE scan-node
properties: PaimonScanPlanProvider.getScanNodeProperties iterated the raw
catalog properties and emitted location.<rawkey> for any s3./oss./cos./obs./
hadoop./fs./dfs./hive. prefix; the fe-core bridge only strips the location.
prefix. BE's native (FILE_S3) reader understands ONLY AWS_ACCESS_KEY/
AWS_SECRET_KEY/AWS_ENDPOINT/AWS_REGION/AWS_TOKEN, so static s3.access_key/
oss.access_key on a private bucket reached BE unintelligible -> no usable
credentials -> 403. This is the third credential seam (static->BE-scan),
missed by both the prior round and the 8 fixes (review §9.3); the catalog-
FileIO seam (FIX-STORAGE-CREDS) and the vended seam (FIX-REST-VENDED) were
already closed.

Root cause: legacy PaimonScanNode.getLocationProperties returns only
CredentialUtils.getBackendPropertiesFromStorageMap(storagePropertiesMap) (the
canonical AWS_*/hadoop/dfs map). The cutover replaced that single normalized
call with a raw prefix-copy loop; the connector cannot import fe-core's
StorageProperties so it had no access to the normalization.

Solution (D-048, user-signed full legacy-parity scope): new no-op-default SPI
ConnectorContext.getBackendStorageProperties(); DefaultConnectorContext returns
getBackendPropertiesFromStorageMap over the storagePropertiesSupplier already
wired in FIX-URI-NORMALIZE (no ctor change, CredentialUtils already imported).
The connector replaces its raw prefix-copy loop with a context-gated overlay of
that map; the vended overlay stays after it (vended wins on collision, legacy
precedence). Object-store creds -> AWS_*; HDFS -> canonical hadoop/dfs
(preserves user overrides + adds the legacy defaults, folding in the §211
MINOR); drops the non-parity hive.* passthrough. Investigated the
AWS_CREDENTIALS_PROVIDER_TYPE=ANONYMOUS two-step edge and confirmed via BE
s3_util.cpp (both providers prefer explicit ak/sk over cred_provider_type) that
it is harmless — no regression. Connector import-gate stays clean.

Tests: fe-core DefaultConnectorContextBackendStoragePropsTest (OSS static creds
-> AWS_*, raw alias absent; no-supplier -> empty); connector
PaimonScanPlanProviderTest (+getScanNodePropertiesNormalizesStaticCreds raw
alias not shipped; modified vended-overlay collision to canonical keys; renamed
no-context test -> emits no storage props). Fail-before/pass-after proven by
reverting the connector change (2/3 go red). Module 217/0/0 (1 CI-gated skip),
checkstyle clean, import-gate clean. Live private-bucket native-read e2e is
CI-gated (not run). SPI RFC §22 (E14).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record FIX-STATIC-CREDS-BE commit d23d5df in the task-list and update
HANDOFF.md (HEAD, migration chain, completed/next sections). Next: #3
FIX-SCHEMA-EVOLUTION (B-1a+M-10) — the largest P0 SPI surface, independent of
#1/#2; recommend a fresh session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ema_info from the connector

Root cause (rereview2 BLOCKER B-1a): on the native (ORC/Parquet) read path the
paimon connector emitted only the per-file TPaimonFileDesc.schema_id but never
set the scan-level TFileScanRangeParams.current_schema_id / history_schema_info.
BE (table_schema_change_helper.h:219-237) then took the !__isset branch and fell
back to NAME-based file<->table column matching, so a schema-evolved (renamed /
reordered) table read NULL/garbage for the renamed columns silently. JNI path is
unaffected; native is the default. (M-10, Column.uniqueId=-1, deferred — DV-026.)

Design C (user-signed D-049): BE's field-id matcher (table_schema_change_helper
.cpp:312-430) reads only TField.id/name and a nested-vs-scalar type.type tag — no
Doris Type, no tuple descriptor — and org.apache.doris.thrift.* is import-legal in
connectors, so the connector builds the TSchema dictionary directly from paimon
SchemaManager and ships it via the existing populateScanLevelParams hook (the seam
DV-006 anticipated for hudi). Zero new SPI surface; connector-only.
  - current_schema_id = -1; history_schema_info = the -1/current (pinned) schema +
    one entry per SchemaManager.listAllIds() so every native file schema_id is
    covered (BE fails loud on a missing entry, never silent).
  - transport: base64 TBinaryProtocol carrier (a throwaway TFileScanRangeParams)
    via a props key, because getScanPlanProvider() is per-call (no shared state).

Clean-room 3-lens review found 2 real BLOCKERs in the -1/current entry (both fixed
+ re-verified): (1) column-name casing — BE keys the table-side StructNode by the
-1 entry's name verbatim while the native reader queries the lowercase Doris slot
name, and current_schema_id=-1 never hits the ConstNode fast-path, so a mixed-case
column crashed (std::out_of_range) even on never-evolved tables; fix lowercases
ONLY top-level names (default-locale, matching the slot-name producer + legacy
parseSchema:507; nested stays paimon-cased per legacy PaimonUtil:302). (2) time
travel — the -1 entry used schemaManager.latest() (absolute latest) instead of the
snapshot-pinned schema the tuple uses; fix builds it from FileStoreTable.schema()
(pinned) and narrows the guard DataTable->FileStoreTable. Eager all-schemas read
accepted as a fail-loud deviation (DV-027).

Tests: PaimonScanPlanProviderTest +5 (field-id/name carriage, nested ARRAY/MAP/
STRUCT shape + struct-child ids, scalar tag, rename round-trip apply, top-level
lowercase vs nested paimon-case, non-FileStoreTable skip). Module 222/0/0 (1
CI-gated skip), checkstyle clean, import-gate clean. e2e
test_paimon_full_schema_change.groovy is CI-gated (not run). Design doc + D-049 +
DV-026/DV-027 + SPI RFC §23 (no new SPI).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…at CREATE (B-8a + B-8b)

rereview2 #4. JDBC-metastore-flavor paimon catalogs only. Connector-only, zero new SPI.

Root cause:
- B-8a (functional BLOCKER): PaimonScanPlanProvider.getBackendPaimonOptions forwarded
  driver_url to BE RAW and its `key.startsWith("jdbc.")` filter dropped the `paimon.jdbc.*`
  alias. A bare `jdbc.driver_url=mysql.jar` reached BE, where JdbcDriverUtils.registerDriver
  does `new URL(value)` -> MalformedURLException; a `paimon.jdbc.driver_url` alias was dropped
  outright. Legacy PaimonJdbcMetaStoreProperties.getBackendPaimonOptions emits
  `jdbc.driver_url=JdbcResource.getFullDriverUrl(driverUrl)` (resolved) + `jdbc.driver_class`.
- B-8b (security): driver_url was loaded into the FE JVM (URLClassLoader) and shipped to BE
  with no format / jdbc_driver_url_white_list / jdbc_driver_secure_path validation, plus a
  stale "paimon is not in SPI_READY_TYPES" disclaimer (false since the B7 cutover added paimon
  to CatalogFactory SPI_READY_TYPES).

Solution (reuses existing hooks; no new SPI surface):
- B-8a: getBackendPaimonOptions now reads driver_url via firstNonBlank(JDBC_DRIVER_URL) (honors
  both the jdbc.* and paimon.jdbc.* alias) and emits the canonical `jdbc.driver_url` RESOLVED to
  a scheme-bearing URL plus `jdbc.driver_class` (BE accepts both alias forms). Resolution is
  extracted to a shared static PaimonCatalogFactory.resolveDriverUrl(driverUrl, env) so FE driver
  registration and the BE-bound options resolve a given driver_url identically.
- B-8b: PaimonConnector overrides Connector.preCreateValidation to route a configured driver_url
  (either alias) through ConnectorValidationContext.validateAndResolveDriverPath at CREATE CATALOG
  (format/whitelist/secure-path; throws -> CREATE fails before the jar loads). Mirrors
  JdbcDorisConnector. Stale disclaimer replaced with an accurate note.

Scope (user-signed D-050; see DV-028/DV-029): validation is CREATE-time only — parity with the
JDBC reference connector. The FE-restart-reload / ALTER-CATALOG / scan-time re-validation gap is a
pre-existing fe-core limitation shared by all plugin connectors (default config is permissive);
accepted, with a cross-connector follow-up filed. BE-side paimon.jdbc.{user,password,uri} alias-drop
is out of scope (BE deserializes the table from serialized_table; only driver_url/driver_class are
consumed by registerDriverIfNeeded).

Tests: PaimonScanPlanProviderTest +5 (resolve bare name, honor paimon.jdbc.* alias, both-aliases
priority+override, preserve scheme-bearing, non-jdbc empty); new PaimonConnectorPreCreateValidationTest
+5 (validate jdbc/alias, skip non-jdbc/no-driver_url, propagate rejection). Module 232/0/0 (1 CI-gated
skip); fail-before verified (5/9 new tests red when neutered); checkstyle 0; connector import-gate clean.
Live e2e (JDBC flavor + remote jar) is CI-gated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#4 FIX-JDBC-DRIVER-URL committed as 2d15b1b (P0 BLOCKERs now all clear).
Fill the #4 task-list commit cell; rewrite HANDOFF to point at #5 (M-crit,
re-verify the dotted-vs-underscore type-mapping key facts before coding).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aimon type-mapping toggles

Root cause: after the SPI cutover the paimon connector reads the type-mapping
toggles from UNDERSCORE keys (enable_mapping_binary_as_varbinary /
enable_mapping_timestamp_tz; PaimonConnectorProperties:39,42 ->
PaimonConnectorMetadata.buildTypeMappingOptions), but fe-core only ever writes
the canonical DOTTED catalog keys (enable.mapping.varbinary /
enable.mapping.timestamp_tz; CatalogProperty:50,52, written/defaulted by
ExternalCatalog.setDefaultPropsIfMissing and hidden via HIDDEN_PROPERTIES).
PluginDrivenExternalCatalog.createConnectorFromProperties hands the connector
the raw catalog property map verbatim, so getOrDefault(underscore,"false") is
always false. Even when the user enables the mapping at CREATE CATALOG, Paimon
BINARY stays STRING and TIMESTAMP_WITH_LOCAL_TIME_ZONE stays DATETIMEV2 — a
silent cutover regression (legacy PaimonExternalTable:350 reads the dotted key
and honors it). The binary key is doubly drifted (separator . -> _ AND token
varbinary -> binary_as_varbinary), so a generic dot->underscore normalizer
would not fix it. Latent until the flag is enabled.

Re-confirmation: M-crit was critic-surfaced (not 3-lens-gated), so the finding
was independently re-verified by a 5-agent scout + adversarial synthesizer
(REAL_BUG, high confidence; false-positive steelman rejected — dotted is
canonical per the original feature PRs, every regression CREATE CATALOG, legacy
parity, and the JDBC connector which kept dotted in the same SPI PR).

Solution (connector-only, zero new SPI, no BE): re-point the two
PaimonConnectorProperties constants to the canonical dotted keys
(ENABLE_MAPPING_VARBINARY = "enable.mapping.varbinary", renamed from
ENABLE_MAPPING_BINARY_AS_VARBINARY to match the CatalogProperty/JDBC/iceberg
convention and fix both separator and token; ENABLE_MAPPING_TIMESTAMP_TZ =
"enable.mapping.timestamp_tz") and update the one reference in
PaimonConnectorMetadata. No logic change — the Options(mapBinaryToVarbinary,
mapTimestampTz) arg order is already correct. BE-side consistency verified:
PluginDrivenScanNode extends FileQueryScanNode and inherits the dotted-key read
for the BE scan param (FileQueryScanNode:192-193,635-678), so FE column type
and BE scan param now agree (they diverged before this fix).

Scope: paimon-only (user-signed D-051). NEW hive + iceberg connectors share the
identical root cause; logged as a cross-connector follow-up (DV-030), not fixed
here. Rejected an fe-core dot->underscore normalizer (broader blast, breaks
JDBC which already reads dotted, and insufficient for paimon's renamed token).

Tests (PaimonConnectorMetadataTest): +2 UT. getTableSchemaHonorsDottedMappingKeys
(bug-catcher) sets the dotted keys true and asserts BINARY->VARBINARY /
LTZ->TIMESTAMPTZ; getTableSchemaDefaultsMappingFlagsOff (guard) asserts the
default-off STRING/DATETIMEV2. Module 234/0/0 (1 CI-gated skip), checkstyle 0,
import-gate clean. Fail-before verified: the bug-catcher reddens on the
underscore key (expected <VARBINARY> but was <STRING>) while the guard stays
green. E2E test_paimon_catalog_{varbinary,timestamp_tz}.groovy are CI-gated
(enablePaimonTest=false + external fixture) — not run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ROS-DOAS

- task-list #5 commit-cell filled with 9dcf6d1
- HANDOFF rewritten: #5 summary + #6 next (two scope questions for the user)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… all read RPCs in doAs (M-11)

Both are Kerberos-only (harmless on simple-auth: the no-op authenticator's
execute() == task.call()).

Root cause
- M-8 (fe-core): paimon filesystem/jdbc catalogs over Kerberized HDFS lost UGI
  doAs on the cutover path. The HDFS HadoopExecutionAuthenticator is built only
  inside initializeCatalog(), which is dead on the plugin path (only legacy
  PaimonExternalCatalog calls it), so PluginDrivenExternalCatalog read the base
  no-op from getExecutionAuthenticator(). HMS was unaffected — it wires the
  authenticator in initNormalizeAndCheckProps(), which always runs.
- M-11 (connector): metadata read RPCs (listDatabases/getDatabase/listTables/
  getTable[handle+sys+resolveTable]/listPartitions) ran without
  executeAuthenticated; only the 4 DDL ops were wrapped (signed D7=B read-vs-DDL
  asymmetry). On a Kerberos HMS catalog these reads ran outside the catalog
  principal. Legacy wrapped every read.

Fix
- M-8 (filesystem+jdbc only; DLF/REST/HMS excluded — DLF uses Aliyun STS not
  Kerberos, the review's "DLF" clause was overstated): new internal fe-core hook
  MetastoreProperties.initExecutionAuthenticator(List<StorageProperties>) (default
  no-op), invoked by PluginDrivenExternalCatalog.initPreExecutionAuthenticator from
  the already-built storage list; filesystem/jdbc override it to build the HDFS
  authenticator (shared AbstractPaimonProperties helper), mirroring HMS. No
  connector change; no connector SPI change.
- M-11 (full legacy parity, signed D-052, supersedes the D7=B read clause): wrap
  all 7 connector read RPCs in context.executeAuthenticated. A single resolveTable
  wrap covers all resolveTable callers (metadata + scan). Domain exceptions are
  caught INSIDE the lambda because Kerberos UGI.doAs wraps a thrown checked
  Catalog.*NotExistException in UndeclaredThrowableException.

Tests
- M-11: PaimonConnectorMetadataReadAuthTest (12) + 2 scan-path tests assert each
  read runs inside executeAuthenticated (RecordingConnectorContext failAuth/
  authCount). Connector module 248/0/0 (1 CI-gated skip).
- M-8: Paimon{FileSystem,Jdbc}MetaStorePropertiesTest assert getExecutionAuthenticator()
  returns HadoopExecutionAuthenticator after wiring without initializeCatalog;
  fe-core metastore-props 21/0/0 (DLF/HMS regression-clean).
- fail-before verified red for both (M-8: stays base no-op AbstractPaimonProperties$1;
  M-11: authCount/log-empty).
- True end-to-end doAs is live-Kerberos-e2e only (no paimon-kerberos suite); DV-031.

Decisions D-052 (M-11) / D-053 (M-8); deviation DV-031; design
plan-doc/tasks/designs/P5-fix-KERBEROS-DOAS-design.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-SCANNER

#6 fix commit = 2b1442f. Fill task-list commit cell; roll HANDOFF to #7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aimon connector scan path (M-1)

Root cause: the cutover (plugin) connector's split router read only the
name-derived handle flag paimonHandle.isForceJni() (the binlog/audit_log
NAME hatch) and never consulted the session var force_jni_scanner, so
ORC/Parquet always took the native reader — legacy's JNI escape hatch
(SET force_jni_scanner=true, used to dodge native-reader bugs incl. the
B2 schema-evolution class) was silently gone. The connector ported only
two of legacy's three native-gate conjuncts (PaimonScanNode.java:430:
!forceJniScanner && !forceJniForSystemTable && supportNativeReader); the
dropped !forceJniScanner conjunct is M-1.

Solution (pure connector; no SPI, no fe-core import, no BE param — legacy
serializes nothing for this var):
- new isForceJniScannerEnabled(session): byte-for-byte mirror of
  isCppReaderEnabled, reads key "force_jni_scanner" (byte-identical to
  SessionVariable.FORCE_JNI_SCANNER) from the same VariableMgr.toMap
  channel; null-guarded, default false (legacy default).
- Site A (correctness): shouldUseNativeReader gains an explicit
  forceJniScanner param (mirrors legacy's sibling boolean 1:1) ANDed into
  the native gate; planScan passes isForceJniScannerEnabled(session). The
  handle name-force is OR-sibling, never replaced (binlog/audit_log intact).
- Site B (correctness-neutral): getScanNodeProperties suppresses the
  native-only paimon.schema_evolution dict when force_jni_scanner routes
  every split to JNI (BE consumes it only on native ORC/Parquet ranges;
  JNI/cpp readers ignore it). Matches the connector's own documented contract.

Tests (fail-before + pass-after both verified):
- isForceJniScannerEnabledReadsSessionProperty: pins the exact key,
  default-false, null-safety.
- forceJniScannerRoutesNativeEligibleSplitToJni: a native-eligible split
  must route to JNI when force_jni_scanner=true (legacy parity).
- 3 existing shouldUseNativeReader calls updated for the new param.
- Module 250/0/0 (+1 CI-gated live skip); connector import-gate + checkstyle clean.
- Real BE reader selection is a CI-gated live-e2e check (no offline coverage).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-COUNT-PUSHDOWN (P2, ask scope first)

- task-list: #7 row → ✅ design/impl/build(250/0/0)/commit `05132a42668` + DONE detail.
- HANDOFF: #7 summary (3rd-param overrides synthesizer call-site-OR per Rule 9;
  Site B correctness-neutral, no offline red test honestly noted); next = apache#8/apache#9
  P2 perf-parity → AskUserQuestion for scope (accept-or-defer) BEFORE implementing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(*) on plugin paimon (M-2)

Root cause: after cutover, COUNT(*) over a plugin-driven paimon table is
result-correct but slow. The COUNT enum already reaches BE
(FileScanNode.toThrift:90; PhysicalPlanTranslator:873 sets it on the plugin
node, not excluded) and the per-range emit seam is already built
(PaimonScanRange.Builder.rowCount -> paimon.row_count -> setTableLevelRowCount,
byte-identical to legacy PaimonScanNode:303-308). The missing half is the
signal + compute: DataSplit.mergedRowCount() is paimon-SDK-only (connector),
and the getPushDownAggNoGroupingOp()==COUNT signal lives only on the fe-core
node and reached nobody. So every split carried table_level_row_count=-1 and
BE materialized the full post-merge row set just to count (file_scanner.cpp:
1298-1326) — costly on PK/MOR tables.

Not pure-connector: the signal must cross the SPI boundary. Threading it via
ConnectorSession (the FIX-FORCE-JNI precedent) was rejected — the agg-op is a
per-query planner output, not a SET-variable, and would be a silent untyped
channel.

Solution (3 files; user signed off, D-054):
- SPI (ConnectorScanPlanProvider): new default planScan overload carrying
  `boolean countPushdown`, delegating to the 6-arg variant — mirrors the
  limit/requiredPartitions extension chain; other connectors are no-op (E15).
- fe-core (PluginDrivenScanNode.getSplits): read
  getPushDownAggNoGroupingOp()==TPushAggOp.COUNT and forward the flag. No
  post-loop math.
- connector (PaimonScanPlanProvider): extract planScanInternal(...,countPushdown)
  (4-arg delegates false, new 7-arg delegates the flag); add the count
  short-circuit as the FIRST routing arm (a count-eligible split must not also
  emit a data range, else BE double-counts vs deletion vectors / PK merge);
  collapse-to-one — sum every count-eligible split's mergedRowCount and emit ONE
  JNI count range bearing the total (= legacy's <=10000 singletonList +
  assignCountToSplits case). New members: static isCountPushdownSplit + buildCountRange.

Param shape = boolean (BE only needs COUNT-vs-not), scope = paimon-only
(default no-op). legacy's >10000 parallel-split trim is intentionally dropped
(connector has no numBackends, an fe-core-only concern) — perf-only divergence,
result identical (DV-032). No new thrift, no BE change.

Tests: connector PaimonScanPlanProviderTest +2 — isCountPushdownSplit eligibility
on a real split (true/2, disabled/false); end-to-end planScan over a PARTITIONED
PK table with asymmetric per-partition counts (2 + 3) asserting collapse-to-one
carrying the SUM (5, unreachable from any single split) and no row_count when the
flag is off. Connector 252/0/0 (1 CI-gated live skip), fe-core compile + checkstyle
0, import-gate clean. Fail-before verified: neuter isCountPushdownSplit->false ->
the count tests red; mutate `countSum +=` -> `=` -> the cross-split-sum assertion
red. Real BE CountReader selection / EXPLAIN = CI-gated live-e2e (existing legacy
paimon count regression covers the BE contract).

Adversarially reviewed (workflow wf_6ead7c2c-b58): one MAJOR caught and fixed
(the collapse/sum test was degenerate on a single-split fixture); two MINORs
refuted (batch-path signal moot for paimon; EXPLAIN count-line drop is cosmetic,
noted in DV-032).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…files for read parallelism (M-3)

Root cause: after cutover, a large native (ORC/Parquet) paimon data file gets
ONE scanner — no intra-file parallelism. The connector's native arm emitted
exactly one PaimonScanRange per RawFile (start=0, length=file.length()). Legacy
PaimonScanNode:434-465 sub-splits each large file via determineTargetFileSplitSize
+ fileSplitter.splitFile. Result is correct (BE reads the whole file either way);
only read parallelism regresses.

Recon (wf_ad764bf6-1c9) confirmed: it is a real gap (ORC/Parquet are
PLAIN/splittable, legacy does sub-split); DV x sub-split is SAFE (paimon
deletion-vector rowids are GLOBAL file row positions, BE native readers report
global positions even within a partial byte range, _kv_cache shares the DV bitmap
across sub-splits keyed by path+offset, iceberg uses the identical machinery on
routinely-split files); and it is pure-connector (the splitter math + 5 session
vars re-stated with plain longs — the connector cannot import fe-core
FileSplitter/SessionVariable).

Solution (pure connector, zero SPI, zero fe-core; D-055):
- Two pure statics: computeFileSplitOffsets(fileLength, targetSplitSize) ports
  FileSplitter.splitFile's specified-size branch byte-for-byte incl. the >1.1D
  tail guard (the last range absorbs a remainder up to 1.1x instead of a tiny
  tail split); determineTargetSplitSize(...) ports determineTargetFileSplitSize +
  applyMaxFileSplitNumLimit (the isBatchMode->0 branch omitted — paimon is never
  batch).
- sessionLong + lazy resolveTargetSplitSize read the 5 file-split session vars via
  the VariableMgr.toMap channel (like isCppReaderEnabled) and sum native-eligible
  file sizes once per scan.
- Native arm: emit one range per [start,length) sub-range via buildNativeRanges,
  attaching the SAME unmodified per-RawFile DeletionFile to EVERY sub-range (DV is
  global-row-position indexed; no offset re-basing). buildNativeRange gains
  (start, length); fileSize stays the whole file length.
- Under COUNT(*) pushdown a native split that is not count-eligible (no precomputed
  merged count, e.g. a DV with null cardinality) is kept WHOLE (target size 0 ->
  one whole-file range), mirroring legacy splittable=!applyCountPushdown.

The split-weight/target-size scheduling nicety is not ported (pre-existing native
path already omitted it; perf/scheduling-only, not correctness) -> DV-033.

Tests: connector PaimonScanPlanProviderTest +6 — computeFileSplitOffsets math
(250MB/64MB->4 with 58MB tail, exact-multiple, small-file-whole, empty, target<=0);
determineTargetSplitSize heuristic (file_split_size override, 32MB<->64MB threshold,
max_file_split_num floor); end-to-end append-only fixture (tiny file_split_size ->
>=2 contiguous sub-ranges tiling [0,fileLength); default -> 1 range); DV on every
sub-range; whole-file under count pushdown. Updated the 3 existing buildNativeRange
call sites to the new signature. Connector 258/0/0 (1 CI-gated live skip),
checkstyle 0, import-gate clean. Fail-before verified: neuter computeFileSplitOffsets
-> the 3 splitting tests red; attach DV only to the first sub-range -> the DV test
red. Real BE multi-range + DV read = CI-gated live-e2e (legacy paimon regression
covers the BE contract; no BE change).

Adversarially reviewed (workflow wf_4ac7479d-39d): 2 confirmed and fixed (the
count-pushdown sub-split parity gap + false comment; the missing DV-on-every-sub-range
test), 2 refuted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… hand off P3 coverage-gap verification

- FIX-COUNT-PUSHDOWN (apache#8, M-2) = 525be03; FIX-NATIVE-SUBSPLIT (apache#9, M-3) = 2f5f467.
- Both recon'd (multi-scout workflow) + adversarially reviewed before commit; each review
  caught a real finding (degenerate test / parity gap) that was fixed.
- P0/P1/P2 all clear. Next: P3 coverage gaps (verify, not fix) — FIX-HMS-CONFRES re-check,
  DDL write parity, ANALYZE/column-stats, split-count accounting, cross-connector follow-ups.
- task-list apache#9 commit hash finalized; HANDOFF overwritten.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rejection in PluginDrivenExternalCatalog.createTable

Root cause: the generic fe-core bridge PluginDrivenExternalCatalog.createTable
collapsed legacy PaimonMetadataOps.performCreateTable's ordered remote-then-local
existence probe into a single `exists` OR that was consumed ONLY by the IF NOT
EXISTS branch. The !IF NOT EXISTS path ignored it and unconditionally called
metadata.createTable. So a table present only in the local FE cache (a case-variant
folded onto an existing name under lower_case_meta_names, absent on a case-sensitive
remote) was CREATED remotely instead of rejected with ERR_TABLE_EXISTS_ERROR --
silent metadata corruption. Found by the P3 plugin-vs-legacy parity audit
(adversarially verified); narrow, backend-dependent trigger (filesystem/jdbc paimon;
HMS lowercases so both sides reject). Generic bridge -> also affects MaxCompute /
future iceberg/hudi.

Solution (fe-core bridge only; zero SPI/connector/BE): split the `exists` OR into
remoteExists/localExists; under !IF NOT EXISTS, when localExists is true throw
ERR_TABLE_EXISTS_ERROR (legacy local-arm parity). A remote-only conflict still falls
through to connector.createTable (case A unchanged). Option-2 surgical (D-056); the
residual case-A / all-DDL-op generic-error-code collapse is pre-existing and out of
scope (DV-034).

Tests: new PluginDrivenExternalCatalogDdlRoutingTest
.testCreateTableLocalConflictWithoutIfNotExistsRejects (local-hit + remote-miss +
!IF NOT EXISTS -> asserts DdlException thrown + metadata.createTable never called +
no edit log). fail-before: exactly 1 new test red ("Expected DdlException...nothing
was thrown"); pass-after: 26/0/0. fe-core checkstyle 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…P3-fix landed)

P3 "go check" done via adversarial audit wf_25450c36-b7a: HMS-CONFRES /
ANALYZE-stats / split-count all PARITY_HOLDS; DDL write surfaced one MAJOR
correctness divergence -> FIX-CREATE-TABLE-LOCAL-CONFLICT (67a9b9d).
Updates HANDOFF for next steps (P4 cleanup / B8 legacy removal /
cross-connector follow-up). No P0/P1/P2/P3 blockers remain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…4 N10.1)

Root cause: the plugin read-direction type mapping
PaimonTypeMapping.toVarcharType used `len >= 65533` to overflow a paimon
VarCharType to STRING, while legacy PaimonUtil.paimonPrimitiveTypeToDorisType
uses `len > 65533`. 65533 == ScalarType.MAX_VARCHAR_LENGTH is the legal
exact-fit max VARCHAR, not the STRING wildcard, so the connector widened
VARCHAR(65533) to STRING — a DESCRIBE / SHOW CREATE TABLE reported-type
divergence (data and read correctness unaffected; STRING is a superset).

Fix: change the boundary `>= 65533` -> `> 65533` to match legacy byte-for-byte
(pure connector, 1 char). The unreachable `len <= 0` defensive guard is kept
untouched (paimon VarCharType min length is 1).

Tests: new read-direction PaimonTypeMappingReadTest pins the boundary intent
(65532 -> VARCHAR(65532); 65533 -> VARCHAR(65533) [the fix]; 65534 -> STRING).
Fail-before exactly the 65533 assertion red ("expected VARCHAR but was STRING");
pass-after green. Full module 260/0/0 (1 CI-gated live skip), checkstyle 0,
connector import-gate clean. No BE/SPI change; reported-type parity otherwise
covered by the CI-gated legacy paimon DESCRIBE regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@morningman morningman merged commit 38e7140 into apache:branch-catalog-spi Jun 20, 2026
35 of 40 checks passed
morningman added a commit that referenced this pull request Jun 20, 2026
… make fe-core paimon-SDK-free (T29) (#64653)

### What & why

Follow-up to #64446 (the Paimon catalog-SPI cutover). After the cutover
a `paimon`
catalog deserializes to `PluginDrivenExternalCatalog` and its tables to
`PluginDrivenMvccExternalTable`, so no legacy `Paimon*` catalog / table
/ scan-node
object is ever instantiated — the entire legacy Paimon subsystem in
fe-core is dead
code. This PR removes it and makes fe-core's dependency tree fully
paimon-SDK-free
(zero `org.apache.paimon` imports, the 5 paimon maven deps dropped).
Mirrors the P4
maxcompute cutover → legacy-removal (#64256).

### Changes

**1. Remove legacy subsystem + sever reverse-refs** (`7632a074e4b`)
- Delete the dead fe-core paimon files: `datasource/paimon/*` (all 5
flavor catalogs,
`PaimonExternalTable`, `PaimonMetadataOps`, `PaimonUtil`,
`source/PaimonScanNode` /
`PaimonSplit` / `PaimonPredicateConverter` / `PaimonSource` /
`PaimonValueConverter`,
`profile/*`), `datasource/metacache/paimon/*`,
`systable/PaimonSysTable`, and the
  legacy-only tests.
- Sever the live reverse-references to the deleted classes, keeping
every `PluginDriven`
/ connector sibling branch and the image/replay keep-set: drop the
`PAIMON` db-creation
switch arm in `ExternalCatalog`; the paimon engine routing in
`ExternalMetaCacheMgr` /
`ExternalMetaCacheRouteResolver`; the dead `PAIMON_EXTERNAL_TABLE`
branch in
`Env.getDdlStmt` (keep the LIVE `PLUGIN_EXTERNAL_TABLE` SHOW CREATE
LOCATION/PROPERTIES
rendering); the `PaimonSysExternalTable` branch in `UserAuthentication`;
the legacy
clauses + `handleShowPaimonTablePartitions()` in `ShowPartitionsCommand`
(keep
`hasPartitionStatsCapability()` and the
`TableType.PAIMON_EXTERNAL_TABLE` enum).
Landed as one compiling unit (mirrors the P4 #64300 precedent:
`PaimonUtils` still
  called the removed `ExternalMetaCacheMgr.paimon()`).
- Inline the `getPaimonCatalogType()` string literals
(`hms`/`filesystem`/`dlf`/`rest`/
`jdbc`) to decouple the still-consumed thin metastore-property classes
from the deleted
  `PaimonExternalCatalog`.

**2. Make fe-core paimon-SDK-free** (`32de7d16702`)
- Strip the dead paimon-SDK catalog-building cluster from
`AbstractPaimonProperties` + the
5 flavors (`initializeCatalog` / `buildCatalogOptions` /
`appendCatalogOptions` /
`getMetastoreType` / `normalizeS3Config` / … — 0 live callers; the
plugin path builds
catalogs connector-side). Keep every LIVE SDK-free duty: `warehouse`
`@ConnectorProperty`,
the Kerberos `executionAuthenticator` doAs wiring (read by
`PluginDrivenExternalCatalog`),
property normalization/validation, `getPaimonCatalogType`,
`Type.PAIMON`.
- Vended credentials: delete `PaimonVendedCredentialsProvider` + its
test + the
`VendedCredentialsFactory` `case PAIMON`. Its SDK methods were dead —
reachable only via
the iceberg-only `getStoragePropertiesMapWithVendedCredentials`; the
real paimon vended
path is the connector's `PaimonScanPlanProvider.extractVendedToken`
(moved off fe-core at
cutover). Relocate its one LIVE duty (the REST "skip static storage map"
gate) to a new
SDK-free `MetastoreProperties.isVendedCredentialsEnabled()` (base=false,
`PaimonRestMetaStoreProperties`=true); `CatalogProperty` routes iceberg
through its
provider (byte-identical) and everything else through the
metastore-props method.
- pom: drop `paimon-core` / `-common` / `-format` / `-s3` / `-jindo`
from `fe-core/pom.xml`.
`fe/pom.xml` `paimon.version` is kept (fe-connector-paimon + BE
paimon-scanner still
  consume it).

**3. Doc-sync** (`58160cbcb56`, `895e214b2bd`) — plan-doc HANDOFF /
PROGRESS / connectors /
P5-T29 legacy-removal design tracking notes.

### Verification

- `mvn -pl :fe-core -am test-compile` (main + test) passes; checkstyle 0
violations;
  connector import-gate (`tools/check-connector-imports.sh`) passes.
- `grep -rn org.apache.paimon fe/fe-core/src` → empty (main and test).
- `mvn -pl :fe-core dependency:tree -Dincludes=org.apache.paimon` →
empty (no paimon dep,
  direct or transitive).
- End-to-end paimon regression (`external_table_p0/paimon`, docker
`enablePaimonTest=true`)
  passed on this branch.
morningman added a commit that referenced this pull request Jun 28, 2026
…) (#64446)

Migrates the legacy in-tree Paimon catalog (fe-core `datasource/paimon`,
`metacache/paimon`, `property/metastore/*Paimon*` and their reverse
references) onto the catalog SPI as a self-contained
`fe-connector-paimon`
plugin, following the maxcompute full-adopter + cutover template.
`paimon`
is added to `SPI_READY_TYPES`, so a Paimon catalog now deserializes to
`PluginDrivenExternalCatalog` and all five functional areas —
normal-table
read, system-table read, DDL, MTMV/MVCC/time-travel (procedures are a
doc-only no-op: fe-core has none) — flow through the generic
`PluginDriven*`
bridge with no Paimon-specific branch in fe-core.

Paimon is the first lakehouse full-adopter to exercise the MVCC /
sys-table /
MTMV SPI surfaces, which become the reusable template for the upcoming
iceberg/hudi migrations, so the cutover is held to byte-parity with the
legacy path. The legacy `datasource/paimon/*` classes are left in place
(now
dead on the cutover path) and removed in a follow-up, mirroring the
maxcompute cutover -> P4 removal.

## 1. Generic connector-SPI surface (fe-connector-api / -spi,
DefaultConnectorContext)

Source-agnostic seams so any connector can express these capabilities;
every
new method is a default no-op/identity, so hive/iceberg/jdbc/maxcompute/
trino/es are byte-for-byte unaffected.

- MVCC/time-travel: `resolveTimeTravel(session, handle,
ConnectorTimeTravelSpec)`
+ `applySnapshot(...)` (threads a pinned snapshot into the handle before
planScan); new immutable `ConnectorTimeTravelSpec` (SNAPSHOT_ID /
TIMESTAMP /
TAG / BRANCH / INCREMENTAL) that fe-core extracts from SQL;
`ConnectorMvccSnapshot`
  carries `schemaId` for schema-evolution-aware time travel.
- System tables: `listSupportedSysTables` / `getSysTableHandle`; fe-core
composes
  the `{base}${sys}` reference name.
- Scan: proportional split-weight
(`getSelfSplitWeight`/`getTargetSplitSize`),
COUNT(*) pushdown (`getPushDownRowCount`, 7-arg `planScan`), native-read
accounting (`isNativeReadRange`), `getDeleteFiles` (VERBOSE
merge-on-read),
`ignorePartitionPruneShortCircuit` (predicate-driven connectors keep
genuine-null
  partitions).
- Metadata: `ConnectorPartitionInfo.fileCount` +
`SUPPORTS_PARTITION_STATS` (rich
SHOW PARTITIONS); `ConnectorColumn.withTimeZone` (WITH_TIMEZONE DESC
marker,
  independent of the timestamp_tz mapping flag); connector cache control
  (`invalidateTable`/`invalidateAll`, `schemaCacheTtlSecondOverride`).
- `ConnectorContext`: six engine-side hooks the connector must not
implement itself
(`loadHiveConfResources`, `vendStorageCredentials`,
`getBackendStorageProperties`,
`normalizeStorageUri` x2, `getStorageProperties`), bound in
`DefaultConnectorContext`
over fe-core's single-source-of-truth credential/URI/HiveConf utilities
(no
  re-ported logic that could drift).

## 2. Pluggable metastore SPI (NEW fe-connector-metastore-api / -spi)

The metastore-side counterpart of the fe-filesystem StorageProperties
SPI: a
Paimon catalog resolves its backend (hive/dlf/rest/jdbc/filesystem) by
ServiceLoader dispatch on `paimon.catalog.type` instead of a fe-core
switch.

- `fe-connector-metastore-api`: hadoop/SDK-free `MetaStoreProperties`
contracts
  (HMS/DLF/REST/JDBC/FileSystem) carrying only Map/scalar facts.
- `fe-connector-metastore-spi`: `MetaStoreProvider<P>` + first-hit
`MetaStoreProviders`
dispatcher + five impls, registered via META-INF/services.
`HmsMetaStorePropertiesImpl`
reproduces the legacy `buildHmsHiveConf` key set and parity-critical
ordering
(storage overlay before the kerberos block; username/sasl last). Adding
a backend =
  one provider class + one services line (no central enum/switch).
- ServiceLoader is loaded with the SPI interface's own (plugin)
classloader, NOT the
thread-context CL: at CREATE CATALOG the static init first fires on an
FE worker
thread whose TCCL is the FE app loader, so a 1-arg load would cache an
empty
  provider list process-wide.
- fe-core bridges add
`initExecutionAuthenticator(List<StorageProperties>)` so
HDFS-Kerberos doAs is wired on the plugin path (legacy
`initializeCatalog` is dead).

## 3. The Paimon connector (fe-connector-paimon)

Full read + DDL + partitions + statistics + system tables +
MVCC/time-travel across
all five metastore flavors, planning native (ORC/Parquet) vs JNI reads.

- `PaimonCatalogFactory` (pure flavor switch: Options + Hadoop
Configuration + HiveConf,
classloader-pinned for child-first loading), `PaimonCatalogOps`
(injection seam over
the remote Catalog for offline-testable metadata),
`PaimonConnectorMetadata` (~9 -> 28
methods: schema latest/at-snapshot, sys tables, MVCC/time-travel, DDL,
partitions,
  statistics, table descriptor).
- `PaimonScanPlanProvider` (~6 -> 30 methods): predicate/projection
pushdown,
native-vs-JNI routing, large-file sub-splitting with deletion vectors,
COUNT(*)
collapse, schema-evolution field-id dictionary, `$ro` unwrap to base
FileStoreTable.
- New: `PaimonSchemaBuilder` (CREATE TABLE),
`PaimonIncrementalScanParams` (@incr),
`PaimonTableResolver` (sys/branch-aware handle -> Table),
`PaimonLatestSnapshotCache`
+ `PaimonSchemaAtMemo` (per-catalog caches on the long-lived connector).
- `PaimonTypeMapping` gains the write direction (Doris -> Paimon) for
CREATE TABLE.

## 4. fe-core PluginDriven host + cutover

- NEW `PluginDrivenMvccExternalTable` (generic MvccTable + MTMV host:
the runtime class
for paimon tables), `PluginDrivenMvccSnapshot`,
`PluginDrivenSysExternalTable` +
`systable/PluginDrivenSysTable`. Behavior is selected by
`ConnectorCapability`
(SUPPORTS_MVCC_SNAPSHOT, SUPPORTS_PARTITION_STATS) — no paimon branch in
fe-core.
- `PluginDrivenScanNode`: MVCC pin threaded at every handle-consumption
site, sys-table
time-travel guard, COUNT pushdown, connector-agnostic EXPLAIN
re-emission.
- Cutover: `CatalogFactory` adds `paimon` to `SPI_READY_TYPES` and drops
the built-in
case; `GsonUtils` removes the 7 built-in `Paimon*` subtype registrations
and adds
replay compat so persisted `PaimonExternalCatalog` (all 5 flavors) /
`...Database`
-> `PluginDriven*` and `PaimonExternalTable` ->
`PluginDrivenMvccExternalTable` on
  deserialization (existing clusters upgrade without losing catalogs).
- Nereids/DDL: `PhysicalPlanTranslator` drops the `PaimonScanNode`
branch;
`UserAuthentication`, `CreateTableInfo`, `ShowPartitionsCommand`
generalized;
`Env.getDdlStmt` renders LOCATION/PROPERTIES for SHOW CREATE (paimon
engine, no
  credential leak).

## 5. Supporting infrastructure

- fe-filesystem: NEW `HdfsFileSystemProperties` / `HdfsConfigFileLoader`
(typed HDFS;
defaults-laden BE map vs defaults-free FE Hadoop-config map so it cannot
clobber a
co-bound object store's fs.s3a.* during a multi-backend merge); S3 MinIO
support +
  legacy tuning defaults; OSS/OBS/COS ANONYMOUS provider-type; new
`FileSystemFactory.bindAllStorageProperties` /
`FileSystemPluginManager.bindAll`.
- NEW `fe-kerberos` leaf module: dependency-free `AuthType` /
`KerberosAuthSpec` facts
  shared across modules without pulling in Hadoop.
- Packaging: NEW `fe-connector-paimon-hive-shade` relocates
`org.apache.thrift` for
HMS 2.3.7; the paimon plugin-zip excludes fe-thrift/libthrift (provided
parent-first)
to avoid a split `TBase` across loaders; self-contained bundle
(hadoop-aws, AWS-SDK,
hadoop-hdfs-client). `paimon.version` (1.3.1) pinned as the single
property across
FE connector / BE paimon-scanner / preload-extensions for FE<->BE
Table/Split serde.
- BE `PaimonJniScanner.getPredicates()` null-predicate backstop
(no-filter scan; pairs
  with the FE producer now always emitting an empty predicate list).

## 6. Behavior-preservation fixes vs the legacy path

Parity fixes surfaced by the SPI cutover (each restores legacy
semantics):

- Storage/credentials: canonical `s3.*`/`oss.*` keys ->
`fs.s3a.*`/`fs.oss.*`
(STORAGE-CREDS); canonical `AWS_*` BE creds (STATIC-CREDS-BE); per-table
vended
DLF/STS token (REST-VENDED); `oss/cos/obs/s3a` -> `s3://` for data +
deletion-vector
paths (URI-NORMALIZE); external `hive.conf.resources` hive-site.xml
(HMS-CONFRES);
  real doAs on filesystem/jdbc + wrapped HMS read RPCs (KERBEROS-DOAS).
- Reads/types: paimon columns forced nullable (READ-NOTNULL);
`VARCHAR(65533)` not
widened to STRING (VARCHAR-BOUNDARY); dotted `enable.mapping.varbinary`
/
`.timestamp_tz` keys (MAPPING-FLAG-KEYS); per-type partition-value
rendering
(NATIVE-PARTVAL); genuine `\N` not coerced to NULL
(PARTITION-NULL-SENTINEL);
field-id history dict for rename-safe native reads (SCHEMA-EVOLUTION);
paimon-native
  split encode under `enable_paimon_cpp_reader` (CPP-READER).
- Planning/perf: restore the `force_jni_scanner` escape hatch
(FORCE-JNI-SCANNER);
intra-file native sub-splitting (NATIVE-SUBSPLIT); precomputed COUNT(*)
row count
(COUNT-PUSHDOWN); table row count for CBO / SHOW TABLE STATUS
(TABLE-STATS).
- Time travel / DDL: `FOR TIME AS OF` under CST/PST session zones
(TZ-ALIAS); resolved
+ validated `driver_url` for jdbc catalogs (JDBC-DRIVER-URL); reject a
local-only
  name conflict on CREATE TABLE (CREATE-TABLE-LOCAL-CONFLICT).

## 7. Doc-sync

plan-doc P5 design / task-list / HANDOFF / deviation tracking notes.

## Tests & verification

- ~70 new FE unit suites: paimon connector (offline recording fakes:
`RecordingPaimonCatalogOps` / `FakePaimonTable`), fe-core PluginDriven*
MVCC /
sys-table / scan-node / split-weight, metastore-spi dispatch +
per-backend
  properties, fe-filesystem HDFS/MinIO/anonymous, fe-kerberos.
- checkstyle 0 violations; connector import-gate passes; FE main+test
compile clean.
- End-to-end paimon behavior is docker-gated (`enablePaimonTest=true`);
the five
functional areas are covered by the `external_table_p0/paimon`
regression suites,
  green on the latest CI run for this branch.
morningman added a commit that referenced this pull request Jun 28, 2026
… make fe-core paimon-SDK-free (T29) (#64653)

### What & why

Follow-up to #64446 (the Paimon catalog-SPI cutover). After the cutover
a `paimon`
catalog deserializes to `PluginDrivenExternalCatalog` and its tables to
`PluginDrivenMvccExternalTable`, so no legacy `Paimon*` catalog / table
/ scan-node
object is ever instantiated — the entire legacy Paimon subsystem in
fe-core is dead
code. This PR removes it and makes fe-core's dependency tree fully
paimon-SDK-free
(zero `org.apache.paimon` imports, the 5 paimon maven deps dropped).
Mirrors the P4
maxcompute cutover → legacy-removal (#64256).

### Changes

**1. Remove legacy subsystem + sever reverse-refs** (`7632a074e4b`)
- Delete the dead fe-core paimon files: `datasource/paimon/*` (all 5
flavor catalogs,
`PaimonExternalTable`, `PaimonMetadataOps`, `PaimonUtil`,
`source/PaimonScanNode` /
`PaimonSplit` / `PaimonPredicateConverter` / `PaimonSource` /
`PaimonValueConverter`,
`profile/*`), `datasource/metacache/paimon/*`,
`systable/PaimonSysTable`, and the
  legacy-only tests.
- Sever the live reverse-references to the deleted classes, keeping
every `PluginDriven`
/ connector sibling branch and the image/replay keep-set: drop the
`PAIMON` db-creation
switch arm in `ExternalCatalog`; the paimon engine routing in
`ExternalMetaCacheMgr` /
`ExternalMetaCacheRouteResolver`; the dead `PAIMON_EXTERNAL_TABLE`
branch in
`Env.getDdlStmt` (keep the LIVE `PLUGIN_EXTERNAL_TABLE` SHOW CREATE
LOCATION/PROPERTIES
rendering); the `PaimonSysExternalTable` branch in `UserAuthentication`;
the legacy
clauses + `handleShowPaimonTablePartitions()` in `ShowPartitionsCommand`
(keep
`hasPartitionStatsCapability()` and the
`TableType.PAIMON_EXTERNAL_TABLE` enum).
Landed as one compiling unit (mirrors the P4 #64300 precedent:
`PaimonUtils` still
  called the removed `ExternalMetaCacheMgr.paimon()`).
- Inline the `getPaimonCatalogType()` string literals
(`hms`/`filesystem`/`dlf`/`rest`/
`jdbc`) to decouple the still-consumed thin metastore-property classes
from the deleted
  `PaimonExternalCatalog`.

**2. Make fe-core paimon-SDK-free** (`32de7d16702`)
- Strip the dead paimon-SDK catalog-building cluster from
`AbstractPaimonProperties` + the
5 flavors (`initializeCatalog` / `buildCatalogOptions` /
`appendCatalogOptions` /
`getMetastoreType` / `normalizeS3Config` / … — 0 live callers; the
plugin path builds
catalogs connector-side). Keep every LIVE SDK-free duty: `warehouse`
`@ConnectorProperty`,
the Kerberos `executionAuthenticator` doAs wiring (read by
`PluginDrivenExternalCatalog`),
property normalization/validation, `getPaimonCatalogType`,
`Type.PAIMON`.
- Vended credentials: delete `PaimonVendedCredentialsProvider` + its
test + the
`VendedCredentialsFactory` `case PAIMON`. Its SDK methods were dead —
reachable only via
the iceberg-only `getStoragePropertiesMapWithVendedCredentials`; the
real paimon vended
path is the connector's `PaimonScanPlanProvider.extractVendedToken`
(moved off fe-core at
cutover). Relocate its one LIVE duty (the REST "skip static storage map"
gate) to a new
SDK-free `MetastoreProperties.isVendedCredentialsEnabled()` (base=false,
`PaimonRestMetaStoreProperties`=true); `CatalogProperty` routes iceberg
through its
provider (byte-identical) and everything else through the
metastore-props method.
- pom: drop `paimon-core` / `-common` / `-format` / `-s3` / `-jindo`
from `fe-core/pom.xml`.
`fe/pom.xml` `paimon.version` is kept (fe-connector-paimon + BE
paimon-scanner still
  consume it).

**3. Doc-sync** (`58160cbcb56`, `895e214b2bd`) — plan-doc HANDOFF /
PROGRESS / connectors /
P5-T29 legacy-removal design tracking notes.

### Verification

- `mvn -pl :fe-core -am test-compile` (main + test) passes; checkstyle 0
violations;
  connector import-gate (`tools/check-connector-imports.sh`) passes.
- `grep -rn org.apache.paimon fe/fe-core/src` → empty (main and test).
- `mvn -pl :fe-core dependency:tree -Dincludes=org.apache.paimon` →
empty (no paimon dep,
  direct or transitive).
- End-to-end paimon regression (`external_table_p0/paimon`, docker
`enablePaimonTest=true`)
  passed on this branch.
morningman added a commit to morningman/doris that referenced this pull request Jun 28, 2026
…ne + clean phase3-module-split cruft + sync main HANDOFF

The metastore/storage property-refactor sub-line is fully done (core 15/15 +
docker gate passed; merged to main via apache#64446 / apache#64653 / apache#64655). Per user, mark
it closed so future tasks (incl. main-line P6/P7) no longer read its planning docs.

- plan-doc/metastore-storage-refactor/: prepend a "🔒 已彻底 CLOSED — 后续勿读"
  banner to all 8 docs (README/HANDOFF/WORKFLOW/PROGRESS/tasks/decisions-log/
  deviations-log/risks). The banner redirects anyone needing metastore-spi current
  state to the code (fe/fe-connector/fe-connector-metastore-spi/) and the main HANDOFF.
- plan-doc/HANDOFF.md: scope note now marks the sub-line CLOSED; the T05/T06/T07
  Q2=B prerequisite mini-recon now points at the CODE (MetaStoreProviders.bind +
  provider impls) instead of the closed sub-line docs; repo-status reflects the
  cleanup; commit-scrub list updated.

Also removed 12 stale, untracked phase3-module-split leftover dirs (a 2026-04
per-backend-module experiment): fe-connector-{iceberg,paimon}-{api,backend-*},
each containing only a gitignored generated .flattened-pom.xml (0 tracked files,
not in the reactor) -- untracked, so no git diff. The legit fe-connector-api and
fe-connector-metastore-api modules are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011mTrPcvMZtFjsxWJM5TRnG
morningman added a commit that referenced this pull request Jul 2, 2026
…) (#64446)

Migrates the legacy in-tree Paimon catalog (fe-core `datasource/paimon`,
`metacache/paimon`, `property/metastore/*Paimon*` and their reverse
references) onto the catalog SPI as a self-contained
`fe-connector-paimon`
plugin, following the maxcompute full-adopter + cutover template.
`paimon`
is added to `SPI_READY_TYPES`, so a Paimon catalog now deserializes to
`PluginDrivenExternalCatalog` and all five functional areas —
normal-table
read, system-table read, DDL, MTMV/MVCC/time-travel (procedures are a
doc-only no-op: fe-core has none) — flow through the generic
`PluginDriven*`
bridge with no Paimon-specific branch in fe-core.

Paimon is the first lakehouse full-adopter to exercise the MVCC /
sys-table /
MTMV SPI surfaces, which become the reusable template for the upcoming
iceberg/hudi migrations, so the cutover is held to byte-parity with the
legacy path. The legacy `datasource/paimon/*` classes are left in place
(now
dead on the cutover path) and removed in a follow-up, mirroring the
maxcompute cutover -> P4 removal.

DefaultConnectorContext)

Source-agnostic seams so any connector can express these capabilities;
every
new method is a default no-op/identity, so hive/iceberg/jdbc/maxcompute/
trino/es are byte-for-byte unaffected.

- MVCC/time-travel: `resolveTimeTravel(session, handle,
ConnectorTimeTravelSpec)`
+ `applySnapshot(...)` (threads a pinned snapshot into the handle before
planScan); new immutable `ConnectorTimeTravelSpec` (SNAPSHOT_ID /
TIMESTAMP /
TAG / BRANCH / INCREMENTAL) that fe-core extracts from SQL;
`ConnectorMvccSnapshot`
  carries `schemaId` for schema-evolution-aware time travel.
- System tables: `listSupportedSysTables` / `getSysTableHandle`; fe-core
composes
  the `{base}${sys}` reference name.
- Scan: proportional split-weight
(`getSelfSplitWeight`/`getTargetSplitSize`),
COUNT(*) pushdown (`getPushDownRowCount`, 7-arg `planScan`), native-read
accounting (`isNativeReadRange`), `getDeleteFiles` (VERBOSE
merge-on-read),
`ignorePartitionPruneShortCircuit` (predicate-driven connectors keep
genuine-null
  partitions).
- Metadata: `ConnectorPartitionInfo.fileCount` +
`SUPPORTS_PARTITION_STATS` (rich
SHOW PARTITIONS); `ConnectorColumn.withTimeZone` (WITH_TIMEZONE DESC
marker,
  independent of the timestamp_tz mapping flag); connector cache control
  (`invalidateTable`/`invalidateAll`, `schemaCacheTtlSecondOverride`).
- `ConnectorContext`: six engine-side hooks the connector must not
implement itself
(`loadHiveConfResources`, `vendStorageCredentials`,
`getBackendStorageProperties`,
`normalizeStorageUri` x2, `getStorageProperties`), bound in
`DefaultConnectorContext`
over fe-core's single-source-of-truth credential/URI/HiveConf utilities
(no
  re-ported logic that could drift).

The metastore-side counterpart of the fe-filesystem StorageProperties
SPI: a
Paimon catalog resolves its backend (hive/dlf/rest/jdbc/filesystem) by
ServiceLoader dispatch on `paimon.catalog.type` instead of a fe-core
switch.

- `fe-connector-metastore-api`: hadoop/SDK-free `MetaStoreProperties`
contracts
  (HMS/DLF/REST/JDBC/FileSystem) carrying only Map/scalar facts.
- `fe-connector-metastore-spi`: `MetaStoreProvider<P>` + first-hit
`MetaStoreProviders`
dispatcher + five impls, registered via META-INF/services.
`HmsMetaStorePropertiesImpl`
reproduces the legacy `buildHmsHiveConf` key set and parity-critical
ordering
(storage overlay before the kerberos block; username/sasl last). Adding
a backend =
  one provider class + one services line (no central enum/switch).
- ServiceLoader is loaded with the SPI interface's own (plugin)
classloader, NOT the
thread-context CL: at CREATE CATALOG the static init first fires on an
FE worker
thread whose TCCL is the FE app loader, so a 1-arg load would cache an
empty
  provider list process-wide.
- fe-core bridges add
`initExecutionAuthenticator(List<StorageProperties>)` so
HDFS-Kerberos doAs is wired on the plugin path (legacy
`initializeCatalog` is dead).

Full read + DDL + partitions + statistics + system tables +
MVCC/time-travel across
all five metastore flavors, planning native (ORC/Parquet) vs JNI reads.

- `PaimonCatalogFactory` (pure flavor switch: Options + Hadoop
Configuration + HiveConf,
classloader-pinned for child-first loading), `PaimonCatalogOps`
(injection seam over
the remote Catalog for offline-testable metadata),
`PaimonConnectorMetadata` (~9 -> 28
methods: schema latest/at-snapshot, sys tables, MVCC/time-travel, DDL,
partitions,
  statistics, table descriptor).
- `PaimonScanPlanProvider` (~6 -> 30 methods): predicate/projection
pushdown,
native-vs-JNI routing, large-file sub-splitting with deletion vectors,
COUNT(*)
collapse, schema-evolution field-id dictionary, `$ro` unwrap to base
FileStoreTable.
- New: `PaimonSchemaBuilder` (CREATE TABLE),
`PaimonIncrementalScanParams` (@incr),
`PaimonTableResolver` (sys/branch-aware handle -> Table),
`PaimonLatestSnapshotCache`
+ `PaimonSchemaAtMemo` (per-catalog caches on the long-lived connector).
- `PaimonTypeMapping` gains the write direction (Doris -> Paimon) for
CREATE TABLE.

- NEW `PluginDrivenMvccExternalTable` (generic MvccTable + MTMV host:
the runtime class
for paimon tables), `PluginDrivenMvccSnapshot`,
`PluginDrivenSysExternalTable` +
`systable/PluginDrivenSysTable`. Behavior is selected by
`ConnectorCapability`
(SUPPORTS_MVCC_SNAPSHOT, SUPPORTS_PARTITION_STATS) — no paimon branch in
fe-core.
- `PluginDrivenScanNode`: MVCC pin threaded at every handle-consumption
site, sys-table
time-travel guard, COUNT pushdown, connector-agnostic EXPLAIN
re-emission.
- Cutover: `CatalogFactory` adds `paimon` to `SPI_READY_TYPES` and drops
the built-in
case; `GsonUtils` removes the 7 built-in `Paimon*` subtype registrations
and adds
replay compat so persisted `PaimonExternalCatalog` (all 5 flavors) /
`...Database`
-> `PluginDriven*` and `PaimonExternalTable` ->
`PluginDrivenMvccExternalTable` on
  deserialization (existing clusters upgrade without losing catalogs).
- Nereids/DDL: `PhysicalPlanTranslator` drops the `PaimonScanNode`
branch;
`UserAuthentication`, `CreateTableInfo`, `ShowPartitionsCommand`
generalized;
`Env.getDdlStmt` renders LOCATION/PROPERTIES for SHOW CREATE (paimon
engine, no
  credential leak).

- fe-filesystem: NEW `HdfsFileSystemProperties` / `HdfsConfigFileLoader`
(typed HDFS;
defaults-laden BE map vs defaults-free FE Hadoop-config map so it cannot
clobber a
co-bound object store's fs.s3a.* during a multi-backend merge); S3 MinIO
support +
  legacy tuning defaults; OSS/OBS/COS ANONYMOUS provider-type; new
`FileSystemFactory.bindAllStorageProperties` /
`FileSystemPluginManager.bindAll`.
- NEW `fe-kerberos` leaf module: dependency-free `AuthType` /
`KerberosAuthSpec` facts
  shared across modules without pulling in Hadoop.
- Packaging: NEW `fe-connector-paimon-hive-shade` relocates
`org.apache.thrift` for
HMS 2.3.7; the paimon plugin-zip excludes fe-thrift/libthrift (provided
parent-first)
to avoid a split `TBase` across loaders; self-contained bundle
(hadoop-aws, AWS-SDK,
hadoop-hdfs-client). `paimon.version` (1.3.1) pinned as the single
property across
FE connector / BE paimon-scanner / preload-extensions for FE<->BE
Table/Split serde.
- BE `PaimonJniScanner.getPredicates()` null-predicate backstop
(no-filter scan; pairs
  with the FE producer now always emitting an empty predicate list).

Parity fixes surfaced by the SPI cutover (each restores legacy
semantics):

- Storage/credentials: canonical `s3.*`/`oss.*` keys ->
`fs.s3a.*`/`fs.oss.*`
(STORAGE-CREDS); canonical `AWS_*` BE creds (STATIC-CREDS-BE); per-table
vended
DLF/STS token (REST-VENDED); `oss/cos/obs/s3a` -> `s3://` for data +
deletion-vector
paths (URI-NORMALIZE); external `hive.conf.resources` hive-site.xml
(HMS-CONFRES);
  real doAs on filesystem/jdbc + wrapped HMS read RPCs (KERBEROS-DOAS).
- Reads/types: paimon columns forced nullable (READ-NOTNULL);
`VARCHAR(65533)` not
widened to STRING (VARCHAR-BOUNDARY); dotted `enable.mapping.varbinary`
/
`.timestamp_tz` keys (MAPPING-FLAG-KEYS); per-type partition-value
rendering
(NATIVE-PARTVAL); genuine `\N` not coerced to NULL
(PARTITION-NULL-SENTINEL);
field-id history dict for rename-safe native reads (SCHEMA-EVOLUTION);
paimon-native
  split encode under `enable_paimon_cpp_reader` (CPP-READER).
- Planning/perf: restore the `force_jni_scanner` escape hatch
(FORCE-JNI-SCANNER);
intra-file native sub-splitting (NATIVE-SUBSPLIT); precomputed COUNT(*)
row count
(COUNT-PUSHDOWN); table row count for CBO / SHOW TABLE STATUS
(TABLE-STATS).
- Time travel / DDL: `FOR TIME AS OF` under CST/PST session zones
(TZ-ALIAS); resolved
+ validated `driver_url` for jdbc catalogs (JDBC-DRIVER-URL); reject a
local-only
  name conflict on CREATE TABLE (CREATE-TABLE-LOCAL-CONFLICT).

plan-doc P5 design / task-list / HANDOFF / deviation tracking notes.

- ~70 new FE unit suites: paimon connector (offline recording fakes:
`RecordingPaimonCatalogOps` / `FakePaimonTable`), fe-core PluginDriven*
MVCC /
sys-table / scan-node / split-weight, metastore-spi dispatch +
per-backend
  properties, fe-filesystem HDFS/MinIO/anonymous, fe-kerberos.
- checkstyle 0 violations; connector import-gate passes; FE main+test
compile clean.
- End-to-end paimon behavior is docker-gated (`enablePaimonTest=true`);
the five
functional areas are covered by the `external_table_p0/paimon`
regression suites,
  green on the latest CI run for this branch.
morningman added a commit that referenced this pull request Jul 2, 2026
… make fe-core paimon-SDK-free (T29) (#64653)

Follow-up to #64446 (the Paimon catalog-SPI cutover). After the cutover
a `paimon`
catalog deserializes to `PluginDrivenExternalCatalog` and its tables to
`PluginDrivenMvccExternalTable`, so no legacy `Paimon*` catalog / table
/ scan-node
object is ever instantiated — the entire legacy Paimon subsystem in
fe-core is dead
code. This PR removes it and makes fe-core's dependency tree fully
paimon-SDK-free
(zero `org.apache.paimon` imports, the 5 paimon maven deps dropped).
Mirrors the P4
maxcompute cutover → legacy-removal (#64256).

**1. Remove legacy subsystem + sever reverse-refs** (`7632a074e4b`)
- Delete the dead fe-core paimon files: `datasource/paimon/*` (all 5
flavor catalogs,
`PaimonExternalTable`, `PaimonMetadataOps`, `PaimonUtil`,
`source/PaimonScanNode` /
`PaimonSplit` / `PaimonPredicateConverter` / `PaimonSource` /
`PaimonValueConverter`,
`profile/*`), `datasource/metacache/paimon/*`,
`systable/PaimonSysTable`, and the
  legacy-only tests.
- Sever the live reverse-references to the deleted classes, keeping
every `PluginDriven`
/ connector sibling branch and the image/replay keep-set: drop the
`PAIMON` db-creation
switch arm in `ExternalCatalog`; the paimon engine routing in
`ExternalMetaCacheMgr` /
`ExternalMetaCacheRouteResolver`; the dead `PAIMON_EXTERNAL_TABLE`
branch in
`Env.getDdlStmt` (keep the LIVE `PLUGIN_EXTERNAL_TABLE` SHOW CREATE
LOCATION/PROPERTIES
rendering); the `PaimonSysExternalTable` branch in `UserAuthentication`;
the legacy
clauses + `handleShowPaimonTablePartitions()` in `ShowPartitionsCommand`
(keep
`hasPartitionStatsCapability()` and the
`TableType.PAIMON_EXTERNAL_TABLE` enum).
Landed as one compiling unit (mirrors the P4 #64300 precedent:
`PaimonUtils` still
  called the removed `ExternalMetaCacheMgr.paimon()`).
- Inline the `getPaimonCatalogType()` string literals
(`hms`/`filesystem`/`dlf`/`rest`/
`jdbc`) to decouple the still-consumed thin metastore-property classes
from the deleted
  `PaimonExternalCatalog`.

**2. Make fe-core paimon-SDK-free** (`32de7d16702`)
- Strip the dead paimon-SDK catalog-building cluster from
`AbstractPaimonProperties` + the
5 flavors (`initializeCatalog` / `buildCatalogOptions` /
`appendCatalogOptions` /
`getMetastoreType` / `normalizeS3Config` / … — 0 live callers; the
plugin path builds
catalogs connector-side). Keep every LIVE SDK-free duty: `warehouse`
`@ConnectorProperty`,
the Kerberos `executionAuthenticator` doAs wiring (read by
`PluginDrivenExternalCatalog`),
property normalization/validation, `getPaimonCatalogType`,
`Type.PAIMON`.
- Vended credentials: delete `PaimonVendedCredentialsProvider` + its
test + the
`VendedCredentialsFactory` `case PAIMON`. Its SDK methods were dead —
reachable only via
the iceberg-only `getStoragePropertiesMapWithVendedCredentials`; the
real paimon vended
path is the connector's `PaimonScanPlanProvider.extractVendedToken`
(moved off fe-core at
cutover). Relocate its one LIVE duty (the REST "skip static storage map"
gate) to a new
SDK-free `MetastoreProperties.isVendedCredentialsEnabled()` (base=false,
`PaimonRestMetaStoreProperties`=true); `CatalogProperty` routes iceberg
through its
provider (byte-identical) and everything else through the
metastore-props method.
- pom: drop `paimon-core` / `-common` / `-format` / `-s3` / `-jindo`
from `fe-core/pom.xml`.
`fe/pom.xml` `paimon.version` is kept (fe-connector-paimon + BE
paimon-scanner still
  consume it).

**3. Doc-sync** (`58160cbcb56`, `895e214b2bd`) — plan-doc HANDOFF /
PROGRESS / connectors /
P5-T29 legacy-removal design tracking notes.

- `mvn -pl :fe-core -am test-compile` (main + test) passes; checkstyle 0
violations;
  connector import-gate (`tools/check-connector-imports.sh`) passes.
- `grep -rn org.apache.paimon fe/fe-core/src` → empty (main and test).
- `mvn -pl :fe-core dependency:tree -Dincludes=org.apache.paimon` →
empty (no paimon dep,
  direct or transitive).
- End-to-end paimon regression (`external_table_p0/paimon`, docker
`enablePaimonTest=true`)
  passed on this branch.
morningman added a commit to morningman/doris that referenced this pull request Jul 2, 2026
…ne + clean phase3-module-split cruft + sync main HANDOFF

The metastore/storage property-refactor sub-line is fully done (core 15/15 +
docker gate passed; merged to main via apache#64446 / apache#64653 / apache#64655). Per user, mark
it closed so future tasks (incl. main-line P6/P7) no longer read its planning docs.

- plan-doc/metastore-storage-refactor/: prepend a "🔒 已彻底 CLOSED — 后续勿读"
  banner to all 8 docs (README/HANDOFF/WORKFLOW/PROGRESS/tasks/decisions-log/
  deviations-log/risks). The banner redirects anyone needing metastore-spi current
  state to the code (fe/fe-connector/fe-connector-metastore-spi/) and the main HANDOFF.
- plan-doc/HANDOFF.md: scope note now marks the sub-line CLOSED; the T05/T06/T07
  Q2=B prerequisite mini-recon now points at the CODE (MetaStoreProviders.bind +
  provider impls) instead of the closed sub-line docs; repo-status reflects the
  cleanup; commit-scrub list updated.

Also removed 12 stale, untracked phase3-module-split leftover dirs (a 2026-04
per-backend-module experiment): fe-connector-{iceberg,paimon}-{api,backend-*},
each containing only a gitignored generated .flattened-pom.xml (0 tracked files,
not in the reactor) -- untracked, so no git diff. The legit fe-connector-api and
fe-connector-metastore-api modules are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011mTrPcvMZtFjsxWJM5TRnG
morningman added a commit that referenced this pull request Jul 3, 2026
…) (#64446)

Migrates the legacy in-tree Paimon catalog (fe-core `datasource/paimon`,
`metacache/paimon`, `property/metastore/*Paimon*` and their reverse
references) onto the catalog SPI as a self-contained
`fe-connector-paimon`
plugin, following the maxcompute full-adopter + cutover template.
`paimon`
is added to `SPI_READY_TYPES`, so a Paimon catalog now deserializes to
`PluginDrivenExternalCatalog` and all five functional areas —
normal-table
read, system-table read, DDL, MTMV/MVCC/time-travel (procedures are a
doc-only no-op: fe-core has none) — flow through the generic
`PluginDriven*`
bridge with no Paimon-specific branch in fe-core.

Paimon is the first lakehouse full-adopter to exercise the MVCC /
sys-table /
MTMV SPI surfaces, which become the reusable template for the upcoming
iceberg/hudi migrations, so the cutover is held to byte-parity with the
legacy path. The legacy `datasource/paimon/*` classes are left in place
(now
dead on the cutover path) and removed in a follow-up, mirroring the
maxcompute cutover -> P4 removal.

DefaultConnectorContext)

Source-agnostic seams so any connector can express these capabilities;
every
new method is a default no-op/identity, so hive/iceberg/jdbc/maxcompute/
trino/es are byte-for-byte unaffected.

- MVCC/time-travel: `resolveTimeTravel(session, handle,
ConnectorTimeTravelSpec)`
+ `applySnapshot(...)` (threads a pinned snapshot into the handle before
planScan); new immutable `ConnectorTimeTravelSpec` (SNAPSHOT_ID /
TIMESTAMP /
TAG / BRANCH / INCREMENTAL) that fe-core extracts from SQL;
`ConnectorMvccSnapshot`
  carries `schemaId` for schema-evolution-aware time travel.
- System tables: `listSupportedSysTables` / `getSysTableHandle`; fe-core
composes
  the `{base}${sys}` reference name.
- Scan: proportional split-weight
(`getSelfSplitWeight`/`getTargetSplitSize`),
COUNT(*) pushdown (`getPushDownRowCount`, 7-arg `planScan`), native-read
accounting (`isNativeReadRange`), `getDeleteFiles` (VERBOSE
merge-on-read),
`ignorePartitionPruneShortCircuit` (predicate-driven connectors keep
genuine-null
  partitions).
- Metadata: `ConnectorPartitionInfo.fileCount` +
`SUPPORTS_PARTITION_STATS` (rich
SHOW PARTITIONS); `ConnectorColumn.withTimeZone` (WITH_TIMEZONE DESC
marker,
  independent of the timestamp_tz mapping flag); connector cache control
  (`invalidateTable`/`invalidateAll`, `schemaCacheTtlSecondOverride`).
- `ConnectorContext`: six engine-side hooks the connector must not
implement itself
(`loadHiveConfResources`, `vendStorageCredentials`,
`getBackendStorageProperties`,
`normalizeStorageUri` x2, `getStorageProperties`), bound in
`DefaultConnectorContext`
over fe-core's single-source-of-truth credential/URI/HiveConf utilities
(no
  re-ported logic that could drift).

The metastore-side counterpart of the fe-filesystem StorageProperties
SPI: a
Paimon catalog resolves its backend (hive/dlf/rest/jdbc/filesystem) by
ServiceLoader dispatch on `paimon.catalog.type` instead of a fe-core
switch.

- `fe-connector-metastore-api`: hadoop/SDK-free `MetaStoreProperties`
contracts
  (HMS/DLF/REST/JDBC/FileSystem) carrying only Map/scalar facts.
- `fe-connector-metastore-spi`: `MetaStoreProvider<P>` + first-hit
`MetaStoreProviders`
dispatcher + five impls, registered via META-INF/services.
`HmsMetaStorePropertiesImpl`
reproduces the legacy `buildHmsHiveConf` key set and parity-critical
ordering
(storage overlay before the kerberos block; username/sasl last). Adding
a backend =
  one provider class + one services line (no central enum/switch).
- ServiceLoader is loaded with the SPI interface's own (plugin)
classloader, NOT the
thread-context CL: at CREATE CATALOG the static init first fires on an
FE worker
thread whose TCCL is the FE app loader, so a 1-arg load would cache an
empty
  provider list process-wide.
- fe-core bridges add
`initExecutionAuthenticator(List<StorageProperties>)` so
HDFS-Kerberos doAs is wired on the plugin path (legacy
`initializeCatalog` is dead).

Full read + DDL + partitions + statistics + system tables +
MVCC/time-travel across
all five metastore flavors, planning native (ORC/Parquet) vs JNI reads.

- `PaimonCatalogFactory` (pure flavor switch: Options + Hadoop
Configuration + HiveConf,
classloader-pinned for child-first loading), `PaimonCatalogOps`
(injection seam over
the remote Catalog for offline-testable metadata),
`PaimonConnectorMetadata` (~9 -> 28
methods: schema latest/at-snapshot, sys tables, MVCC/time-travel, DDL,
partitions,
  statistics, table descriptor).
- `PaimonScanPlanProvider` (~6 -> 30 methods): predicate/projection
pushdown,
native-vs-JNI routing, large-file sub-splitting with deletion vectors,
COUNT(*)
collapse, schema-evolution field-id dictionary, `$ro` unwrap to base
FileStoreTable.
- New: `PaimonSchemaBuilder` (CREATE TABLE),
`PaimonIncrementalScanParams` (@incr),
`PaimonTableResolver` (sys/branch-aware handle -> Table),
`PaimonLatestSnapshotCache`
+ `PaimonSchemaAtMemo` (per-catalog caches on the long-lived connector).
- `PaimonTypeMapping` gains the write direction (Doris -> Paimon) for
CREATE TABLE.

- NEW `PluginDrivenMvccExternalTable` (generic MvccTable + MTMV host:
the runtime class
for paimon tables), `PluginDrivenMvccSnapshot`,
`PluginDrivenSysExternalTable` +
`systable/PluginDrivenSysTable`. Behavior is selected by
`ConnectorCapability`
(SUPPORTS_MVCC_SNAPSHOT, SUPPORTS_PARTITION_STATS) — no paimon branch in
fe-core.
- `PluginDrivenScanNode`: MVCC pin threaded at every handle-consumption
site, sys-table
time-travel guard, COUNT pushdown, connector-agnostic EXPLAIN
re-emission.
- Cutover: `CatalogFactory` adds `paimon` to `SPI_READY_TYPES` and drops
the built-in
case; `GsonUtils` removes the 7 built-in `Paimon*` subtype registrations
and adds
replay compat so persisted `PaimonExternalCatalog` (all 5 flavors) /
`...Database`
-> `PluginDriven*` and `PaimonExternalTable` ->
`PluginDrivenMvccExternalTable` on
  deserialization (existing clusters upgrade without losing catalogs).
- Nereids/DDL: `PhysicalPlanTranslator` drops the `PaimonScanNode`
branch;
`UserAuthentication`, `CreateTableInfo`, `ShowPartitionsCommand`
generalized;
`Env.getDdlStmt` renders LOCATION/PROPERTIES for SHOW CREATE (paimon
engine, no
  credential leak).

- fe-filesystem: NEW `HdfsFileSystemProperties` / `HdfsConfigFileLoader`
(typed HDFS;
defaults-laden BE map vs defaults-free FE Hadoop-config map so it cannot
clobber a
co-bound object store's fs.s3a.* during a multi-backend merge); S3 MinIO
support +
  legacy tuning defaults; OSS/OBS/COS ANONYMOUS provider-type; new
`FileSystemFactory.bindAllStorageProperties` /
`FileSystemPluginManager.bindAll`.
- NEW `fe-kerberos` leaf module: dependency-free `AuthType` /
`KerberosAuthSpec` facts
  shared across modules without pulling in Hadoop.
- Packaging: NEW `fe-connector-paimon-hive-shade` relocates
`org.apache.thrift` for
HMS 2.3.7; the paimon plugin-zip excludes fe-thrift/libthrift (provided
parent-first)
to avoid a split `TBase` across loaders; self-contained bundle
(hadoop-aws, AWS-SDK,
hadoop-hdfs-client). `paimon.version` (1.3.1) pinned as the single
property across
FE connector / BE paimon-scanner / preload-extensions for FE<->BE
Table/Split serde.
- BE `PaimonJniScanner.getPredicates()` null-predicate backstop
(no-filter scan; pairs
  with the FE producer now always emitting an empty predicate list).

Parity fixes surfaced by the SPI cutover (each restores legacy
semantics):

- Storage/credentials: canonical `s3.*`/`oss.*` keys ->
`fs.s3a.*`/`fs.oss.*`
(STORAGE-CREDS); canonical `AWS_*` BE creds (STATIC-CREDS-BE); per-table
vended
DLF/STS token (REST-VENDED); `oss/cos/obs/s3a` -> `s3://` for data +
deletion-vector
paths (URI-NORMALIZE); external `hive.conf.resources` hive-site.xml
(HMS-CONFRES);
  real doAs on filesystem/jdbc + wrapped HMS read RPCs (KERBEROS-DOAS).
- Reads/types: paimon columns forced nullable (READ-NOTNULL);
`VARCHAR(65533)` not
widened to STRING (VARCHAR-BOUNDARY); dotted `enable.mapping.varbinary`
/
`.timestamp_tz` keys (MAPPING-FLAG-KEYS); per-type partition-value
rendering
(NATIVE-PARTVAL); genuine `\N` not coerced to NULL
(PARTITION-NULL-SENTINEL);
field-id history dict for rename-safe native reads (SCHEMA-EVOLUTION);
paimon-native
  split encode under `enable_paimon_cpp_reader` (CPP-READER).
- Planning/perf: restore the `force_jni_scanner` escape hatch
(FORCE-JNI-SCANNER);
intra-file native sub-splitting (NATIVE-SUBSPLIT); precomputed COUNT(*)
row count
(COUNT-PUSHDOWN); table row count for CBO / SHOW TABLE STATUS
(TABLE-STATS).
- Time travel / DDL: `FOR TIME AS OF` under CST/PST session zones
(TZ-ALIAS); resolved
+ validated `driver_url` for jdbc catalogs (JDBC-DRIVER-URL); reject a
local-only
  name conflict on CREATE TABLE (CREATE-TABLE-LOCAL-CONFLICT).

plan-doc P5 design / task-list / HANDOFF / deviation tracking notes.

- ~70 new FE unit suites: paimon connector (offline recording fakes:
`RecordingPaimonCatalogOps` / `FakePaimonTable`), fe-core PluginDriven*
MVCC /
sys-table / scan-node / split-weight, metastore-spi dispatch +
per-backend
  properties, fe-filesystem HDFS/MinIO/anonymous, fe-kerberos.
- checkstyle 0 violations; connector import-gate passes; FE main+test
compile clean.
- End-to-end paimon behavior is docker-gated (`enablePaimonTest=true`);
the five
functional areas are covered by the `external_table_p0/paimon`
regression suites,
  green on the latest CI run for this branch.
morningman added a commit that referenced this pull request Jul 3, 2026
… make fe-core paimon-SDK-free (T29) (#64653)

Follow-up to #64446 (the Paimon catalog-SPI cutover). After the cutover
a `paimon`
catalog deserializes to `PluginDrivenExternalCatalog` and its tables to
`PluginDrivenMvccExternalTable`, so no legacy `Paimon*` catalog / table
/ scan-node
object is ever instantiated — the entire legacy Paimon subsystem in
fe-core is dead
code. This PR removes it and makes fe-core's dependency tree fully
paimon-SDK-free
(zero `org.apache.paimon` imports, the 5 paimon maven deps dropped).
Mirrors the P4
maxcompute cutover → legacy-removal (#64256).

**1. Remove legacy subsystem + sever reverse-refs** (`7632a074e4b`)
- Delete the dead fe-core paimon files: `datasource/paimon/*` (all 5
flavor catalogs,
`PaimonExternalTable`, `PaimonMetadataOps`, `PaimonUtil`,
`source/PaimonScanNode` /
`PaimonSplit` / `PaimonPredicateConverter` / `PaimonSource` /
`PaimonValueConverter`,
`profile/*`), `datasource/metacache/paimon/*`,
`systable/PaimonSysTable`, and the
  legacy-only tests.
- Sever the live reverse-references to the deleted classes, keeping
every `PluginDriven`
/ connector sibling branch and the image/replay keep-set: drop the
`PAIMON` db-creation
switch arm in `ExternalCatalog`; the paimon engine routing in
`ExternalMetaCacheMgr` /
`ExternalMetaCacheRouteResolver`; the dead `PAIMON_EXTERNAL_TABLE`
branch in
`Env.getDdlStmt` (keep the LIVE `PLUGIN_EXTERNAL_TABLE` SHOW CREATE
LOCATION/PROPERTIES
rendering); the `PaimonSysExternalTable` branch in `UserAuthentication`;
the legacy
clauses + `handleShowPaimonTablePartitions()` in `ShowPartitionsCommand`
(keep
`hasPartitionStatsCapability()` and the
`TableType.PAIMON_EXTERNAL_TABLE` enum).
Landed as one compiling unit (mirrors the P4 #64300 precedent:
`PaimonUtils` still
  called the removed `ExternalMetaCacheMgr.paimon()`).
- Inline the `getPaimonCatalogType()` string literals
(`hms`/`filesystem`/`dlf`/`rest`/
`jdbc`) to decouple the still-consumed thin metastore-property classes
from the deleted
  `PaimonExternalCatalog`.

**2. Make fe-core paimon-SDK-free** (`32de7d16702`)
- Strip the dead paimon-SDK catalog-building cluster from
`AbstractPaimonProperties` + the
5 flavors (`initializeCatalog` / `buildCatalogOptions` /
`appendCatalogOptions` /
`getMetastoreType` / `normalizeS3Config` / … — 0 live callers; the
plugin path builds
catalogs connector-side). Keep every LIVE SDK-free duty: `warehouse`
`@ConnectorProperty`,
the Kerberos `executionAuthenticator` doAs wiring (read by
`PluginDrivenExternalCatalog`),
property normalization/validation, `getPaimonCatalogType`,
`Type.PAIMON`.
- Vended credentials: delete `PaimonVendedCredentialsProvider` + its
test + the
`VendedCredentialsFactory` `case PAIMON`. Its SDK methods were dead —
reachable only via
the iceberg-only `getStoragePropertiesMapWithVendedCredentials`; the
real paimon vended
path is the connector's `PaimonScanPlanProvider.extractVendedToken`
(moved off fe-core at
cutover). Relocate its one LIVE duty (the REST "skip static storage map"
gate) to a new
SDK-free `MetastoreProperties.isVendedCredentialsEnabled()` (base=false,
`PaimonRestMetaStoreProperties`=true); `CatalogProperty` routes iceberg
through its
provider (byte-identical) and everything else through the
metastore-props method.
- pom: drop `paimon-core` / `-common` / `-format` / `-s3` / `-jindo`
from `fe-core/pom.xml`.
`fe/pom.xml` `paimon.version` is kept (fe-connector-paimon + BE
paimon-scanner still
  consume it).

**3. Doc-sync** (`58160cbcb56`, `895e214b2bd`) — plan-doc HANDOFF /
PROGRESS / connectors /
P5-T29 legacy-removal design tracking notes.

- `mvn -pl :fe-core -am test-compile` (main + test) passes; checkstyle 0
violations;
  connector import-gate (`tools/check-connector-imports.sh`) passes.
- `grep -rn org.apache.paimon fe/fe-core/src` → empty (main and test).
- `mvn -pl :fe-core dependency:tree -Dincludes=org.apache.paimon` →
empty (no paimon dep,
  direct or transitive).
- End-to-end paimon regression (`external_table_p0/paimon`, docker
`enablePaimonTest=true`)
  passed on this branch.
morningman added a commit to morningman/doris that referenced this pull request Jul 3, 2026
…ne + clean phase3-module-split cruft + sync main HANDOFF

The metastore/storage property-refactor sub-line is fully done (core 15/15 +
docker gate passed; merged to main via apache#64446 / apache#64653 / apache#64655). Per user, mark
it closed so future tasks (incl. main-line P6/P7) no longer read its planning docs.

- plan-doc/metastore-storage-refactor/: prepend a "🔒 已彻底 CLOSED — 后续勿读"
  banner to all 8 docs (README/HANDOFF/WORKFLOW/PROGRESS/tasks/decisions-log/
  deviations-log/risks). The banner redirects anyone needing metastore-spi current
  state to the code (fe/fe-connector/fe-connector-metastore-spi/) and the main HANDOFF.
- plan-doc/HANDOFF.md: scope note now marks the sub-line CLOSED; the T05/T06/T07
  Q2=B prerequisite mini-recon now points at the CODE (MetaStoreProviders.bind +
  provider impls) instead of the closed sub-line docs; repo-status reflects the
  cleanup; commit-scrub list updated.

Also removed 12 stale, untracked phase3-module-split leftover dirs (a 2026-04
per-backend-module experiment): fe-connector-{iceberg,paimon}-{api,backend-*},
each containing only a gitignored generated .flattened-pom.xml (0 tracked files,
not in the reactor) -- untracked, so no git diff. The legit fe-connector-api and
fe-connector-metastore-api modules are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011mTrPcvMZtFjsxWJM5TRnG
morningman added a commit that referenced this pull request Jul 7, 2026
…) (#64446)

Migrates the legacy in-tree Paimon catalog (fe-core `datasource/paimon`,
`metacache/paimon`, `property/metastore/*Paimon*` and their reverse
references) onto the catalog SPI as a self-contained
`fe-connector-paimon`
plugin, following the maxcompute full-adopter + cutover template.
`paimon`
is added to `SPI_READY_TYPES`, so a Paimon catalog now deserializes to
`PluginDrivenExternalCatalog` and all five functional areas —
normal-table
read, system-table read, DDL, MTMV/MVCC/time-travel (procedures are a
doc-only no-op: fe-core has none) — flow through the generic
`PluginDriven*`
bridge with no Paimon-specific branch in fe-core.

Paimon is the first lakehouse full-adopter to exercise the MVCC /
sys-table /
MTMV SPI surfaces, which become the reusable template for the upcoming
iceberg/hudi migrations, so the cutover is held to byte-parity with the
legacy path. The legacy `datasource/paimon/*` classes are left in place
(now
dead on the cutover path) and removed in a follow-up, mirroring the
maxcompute cutover -> P4 removal.

DefaultConnectorContext)

Source-agnostic seams so any connector can express these capabilities;
every
new method is a default no-op/identity, so hive/iceberg/jdbc/maxcompute/
trino/es are byte-for-byte unaffected.

- MVCC/time-travel: `resolveTimeTravel(session, handle,
ConnectorTimeTravelSpec)`
+ `applySnapshot(...)` (threads a pinned snapshot into the handle before
planScan); new immutable `ConnectorTimeTravelSpec` (SNAPSHOT_ID /
TIMESTAMP /
TAG / BRANCH / INCREMENTAL) that fe-core extracts from SQL;
`ConnectorMvccSnapshot`
  carries `schemaId` for schema-evolution-aware time travel.
- System tables: `listSupportedSysTables` / `getSysTableHandle`; fe-core
composes
  the `{base}${sys}` reference name.
- Scan: proportional split-weight
(`getSelfSplitWeight`/`getTargetSplitSize`),
COUNT(*) pushdown (`getPushDownRowCount`, 7-arg `planScan`), native-read
accounting (`isNativeReadRange`), `getDeleteFiles` (VERBOSE
merge-on-read),
`ignorePartitionPruneShortCircuit` (predicate-driven connectors keep
genuine-null
  partitions).
- Metadata: `ConnectorPartitionInfo.fileCount` +
`SUPPORTS_PARTITION_STATS` (rich
SHOW PARTITIONS); `ConnectorColumn.withTimeZone` (WITH_TIMEZONE DESC
marker,
  independent of the timestamp_tz mapping flag); connector cache control
  (`invalidateTable`/`invalidateAll`, `schemaCacheTtlSecondOverride`).
- `ConnectorContext`: six engine-side hooks the connector must not
implement itself
(`loadHiveConfResources`, `vendStorageCredentials`,
`getBackendStorageProperties`,
`normalizeStorageUri` x2, `getStorageProperties`), bound in
`DefaultConnectorContext`
over fe-core's single-source-of-truth credential/URI/HiveConf utilities
(no
  re-ported logic that could drift).

The metastore-side counterpart of the fe-filesystem StorageProperties
SPI: a
Paimon catalog resolves its backend (hive/dlf/rest/jdbc/filesystem) by
ServiceLoader dispatch on `paimon.catalog.type` instead of a fe-core
switch.

- `fe-connector-metastore-api`: hadoop/SDK-free `MetaStoreProperties`
contracts
  (HMS/DLF/REST/JDBC/FileSystem) carrying only Map/scalar facts.
- `fe-connector-metastore-spi`: `MetaStoreProvider<P>` + first-hit
`MetaStoreProviders`
dispatcher + five impls, registered via META-INF/services.
`HmsMetaStorePropertiesImpl`
reproduces the legacy `buildHmsHiveConf` key set and parity-critical
ordering
(storage overlay before the kerberos block; username/sasl last). Adding
a backend =
  one provider class + one services line (no central enum/switch).
- ServiceLoader is loaded with the SPI interface's own (plugin)
classloader, NOT the
thread-context CL: at CREATE CATALOG the static init first fires on an
FE worker
thread whose TCCL is the FE app loader, so a 1-arg load would cache an
empty
  provider list process-wide.
- fe-core bridges add
`initExecutionAuthenticator(List<StorageProperties>)` so
HDFS-Kerberos doAs is wired on the plugin path (legacy
`initializeCatalog` is dead).

Full read + DDL + partitions + statistics + system tables +
MVCC/time-travel across
all five metastore flavors, planning native (ORC/Parquet) vs JNI reads.

- `PaimonCatalogFactory` (pure flavor switch: Options + Hadoop
Configuration + HiveConf,
classloader-pinned for child-first loading), `PaimonCatalogOps`
(injection seam over
the remote Catalog for offline-testable metadata),
`PaimonConnectorMetadata` (~9 -> 28
methods: schema latest/at-snapshot, sys tables, MVCC/time-travel, DDL,
partitions,
  statistics, table descriptor).
- `PaimonScanPlanProvider` (~6 -> 30 methods): predicate/projection
pushdown,
native-vs-JNI routing, large-file sub-splitting with deletion vectors,
COUNT(*)
collapse, schema-evolution field-id dictionary, `$ro` unwrap to base
FileStoreTable.
- New: `PaimonSchemaBuilder` (CREATE TABLE),
`PaimonIncrementalScanParams` (@incr),
`PaimonTableResolver` (sys/branch-aware handle -> Table),
`PaimonLatestSnapshotCache`
+ `PaimonSchemaAtMemo` (per-catalog caches on the long-lived connector).
- `PaimonTypeMapping` gains the write direction (Doris -> Paimon) for
CREATE TABLE.

- NEW `PluginDrivenMvccExternalTable` (generic MvccTable + MTMV host:
the runtime class
for paimon tables), `PluginDrivenMvccSnapshot`,
`PluginDrivenSysExternalTable` +
`systable/PluginDrivenSysTable`. Behavior is selected by
`ConnectorCapability`
(SUPPORTS_MVCC_SNAPSHOT, SUPPORTS_PARTITION_STATS) — no paimon branch in
fe-core.
- `PluginDrivenScanNode`: MVCC pin threaded at every handle-consumption
site, sys-table
time-travel guard, COUNT pushdown, connector-agnostic EXPLAIN
re-emission.
- Cutover: `CatalogFactory` adds `paimon` to `SPI_READY_TYPES` and drops
the built-in
case; `GsonUtils` removes the 7 built-in `Paimon*` subtype registrations
and adds
replay compat so persisted `PaimonExternalCatalog` (all 5 flavors) /
`...Database`
-> `PluginDriven*` and `PaimonExternalTable` ->
`PluginDrivenMvccExternalTable` on
  deserialization (existing clusters upgrade without losing catalogs).
- Nereids/DDL: `PhysicalPlanTranslator` drops the `PaimonScanNode`
branch;
`UserAuthentication`, `CreateTableInfo`, `ShowPartitionsCommand`
generalized;
`Env.getDdlStmt` renders LOCATION/PROPERTIES for SHOW CREATE (paimon
engine, no
  credential leak).

- fe-filesystem: NEW `HdfsFileSystemProperties` / `HdfsConfigFileLoader`
(typed HDFS;
defaults-laden BE map vs defaults-free FE Hadoop-config map so it cannot
clobber a
co-bound object store's fs.s3a.* during a multi-backend merge); S3 MinIO
support +
  legacy tuning defaults; OSS/OBS/COS ANONYMOUS provider-type; new
`FileSystemFactory.bindAllStorageProperties` /
`FileSystemPluginManager.bindAll`.
- NEW `fe-kerberos` leaf module: dependency-free `AuthType` /
`KerberosAuthSpec` facts
  shared across modules without pulling in Hadoop.
- Packaging: NEW `fe-connector-paimon-hive-shade` relocates
`org.apache.thrift` for
HMS 2.3.7; the paimon plugin-zip excludes fe-thrift/libthrift (provided
parent-first)
to avoid a split `TBase` across loaders; self-contained bundle
(hadoop-aws, AWS-SDK,
hadoop-hdfs-client). `paimon.version` (1.3.1) pinned as the single
property across
FE connector / BE paimon-scanner / preload-extensions for FE<->BE
Table/Split serde.
- BE `PaimonJniScanner.getPredicates()` null-predicate backstop
(no-filter scan; pairs
  with the FE producer now always emitting an empty predicate list).

Parity fixes surfaced by the SPI cutover (each restores legacy
semantics):

- Storage/credentials: canonical `s3.*`/`oss.*` keys ->
`fs.s3a.*`/`fs.oss.*`
(STORAGE-CREDS); canonical `AWS_*` BE creds (STATIC-CREDS-BE); per-table
vended
DLF/STS token (REST-VENDED); `oss/cos/obs/s3a` -> `s3://` for data +
deletion-vector
paths (URI-NORMALIZE); external `hive.conf.resources` hive-site.xml
(HMS-CONFRES);
  real doAs on filesystem/jdbc + wrapped HMS read RPCs (KERBEROS-DOAS).
- Reads/types: paimon columns forced nullable (READ-NOTNULL);
`VARCHAR(65533)` not
widened to STRING (VARCHAR-BOUNDARY); dotted `enable.mapping.varbinary`
/
`.timestamp_tz` keys (MAPPING-FLAG-KEYS); per-type partition-value
rendering
(NATIVE-PARTVAL); genuine `\N` not coerced to NULL
(PARTITION-NULL-SENTINEL);
field-id history dict for rename-safe native reads (SCHEMA-EVOLUTION);
paimon-native
  split encode under `enable_paimon_cpp_reader` (CPP-READER).
- Planning/perf: restore the `force_jni_scanner` escape hatch
(FORCE-JNI-SCANNER);
intra-file native sub-splitting (NATIVE-SUBSPLIT); precomputed COUNT(*)
row count
(COUNT-PUSHDOWN); table row count for CBO / SHOW TABLE STATUS
(TABLE-STATS).
- Time travel / DDL: `FOR TIME AS OF` under CST/PST session zones
(TZ-ALIAS); resolved
+ validated `driver_url` for jdbc catalogs (JDBC-DRIVER-URL); reject a
local-only
  name conflict on CREATE TABLE (CREATE-TABLE-LOCAL-CONFLICT).

plan-doc P5 design / task-list / HANDOFF / deviation tracking notes.

- ~70 new FE unit suites: paimon connector (offline recording fakes:
`RecordingPaimonCatalogOps` / `FakePaimonTable`), fe-core PluginDriven*
MVCC /
sys-table / scan-node / split-weight, metastore-spi dispatch +
per-backend
  properties, fe-filesystem HDFS/MinIO/anonymous, fe-kerberos.
- checkstyle 0 violations; connector import-gate passes; FE main+test
compile clean.
- End-to-end paimon behavior is docker-gated (`enablePaimonTest=true`);
the five
functional areas are covered by the `external_table_p0/paimon`
regression suites,
  green on the latest CI run for this branch.
morningman added a commit that referenced this pull request Jul 7, 2026
… make fe-core paimon-SDK-free (T29) (#64653)

Follow-up to #64446 (the Paimon catalog-SPI cutover). After the cutover
a `paimon`
catalog deserializes to `PluginDrivenExternalCatalog` and its tables to
`PluginDrivenMvccExternalTable`, so no legacy `Paimon*` catalog / table
/ scan-node
object is ever instantiated — the entire legacy Paimon subsystem in
fe-core is dead
code. This PR removes it and makes fe-core's dependency tree fully
paimon-SDK-free
(zero `org.apache.paimon` imports, the 5 paimon maven deps dropped).
Mirrors the P4
maxcompute cutover → legacy-removal (#64256).

**1. Remove legacy subsystem + sever reverse-refs** (`7632a074e4b`)
- Delete the dead fe-core paimon files: `datasource/paimon/*` (all 5
flavor catalogs,
`PaimonExternalTable`, `PaimonMetadataOps`, `PaimonUtil`,
`source/PaimonScanNode` /
`PaimonSplit` / `PaimonPredicateConverter` / `PaimonSource` /
`PaimonValueConverter`,
`profile/*`), `datasource/metacache/paimon/*`,
`systable/PaimonSysTable`, and the
  legacy-only tests.
- Sever the live reverse-references to the deleted classes, keeping
every `PluginDriven`
/ connector sibling branch and the image/replay keep-set: drop the
`PAIMON` db-creation
switch arm in `ExternalCatalog`; the paimon engine routing in
`ExternalMetaCacheMgr` /
`ExternalMetaCacheRouteResolver`; the dead `PAIMON_EXTERNAL_TABLE`
branch in
`Env.getDdlStmt` (keep the LIVE `PLUGIN_EXTERNAL_TABLE` SHOW CREATE
LOCATION/PROPERTIES
rendering); the `PaimonSysExternalTable` branch in `UserAuthentication`;
the legacy
clauses + `handleShowPaimonTablePartitions()` in `ShowPartitionsCommand`
(keep
`hasPartitionStatsCapability()` and the
`TableType.PAIMON_EXTERNAL_TABLE` enum).
Landed as one compiling unit (mirrors the P4 #64300 precedent:
`PaimonUtils` still
  called the removed `ExternalMetaCacheMgr.paimon()`).
- Inline the `getPaimonCatalogType()` string literals
(`hms`/`filesystem`/`dlf`/`rest`/
`jdbc`) to decouple the still-consumed thin metastore-property classes
from the deleted
  `PaimonExternalCatalog`.

**2. Make fe-core paimon-SDK-free** (`32de7d16702`)
- Strip the dead paimon-SDK catalog-building cluster from
`AbstractPaimonProperties` + the
5 flavors (`initializeCatalog` / `buildCatalogOptions` /
`appendCatalogOptions` /
`getMetastoreType` / `normalizeS3Config` / … — 0 live callers; the
plugin path builds
catalogs connector-side). Keep every LIVE SDK-free duty: `warehouse`
`@ConnectorProperty`,
the Kerberos `executionAuthenticator` doAs wiring (read by
`PluginDrivenExternalCatalog`),
property normalization/validation, `getPaimonCatalogType`,
`Type.PAIMON`.
- Vended credentials: delete `PaimonVendedCredentialsProvider` + its
test + the
`VendedCredentialsFactory` `case PAIMON`. Its SDK methods were dead —
reachable only via
the iceberg-only `getStoragePropertiesMapWithVendedCredentials`; the
real paimon vended
path is the connector's `PaimonScanPlanProvider.extractVendedToken`
(moved off fe-core at
cutover). Relocate its one LIVE duty (the REST "skip static storage map"
gate) to a new
SDK-free `MetastoreProperties.isVendedCredentialsEnabled()` (base=false,
`PaimonRestMetaStoreProperties`=true); `CatalogProperty` routes iceberg
through its
provider (byte-identical) and everything else through the
metastore-props method.
- pom: drop `paimon-core` / `-common` / `-format` / `-s3` / `-jindo`
from `fe-core/pom.xml`.
`fe/pom.xml` `paimon.version` is kept (fe-connector-paimon + BE
paimon-scanner still
  consume it).

**3. Doc-sync** (`58160cbcb56`, `895e214b2bd`) — plan-doc HANDOFF /
PROGRESS / connectors /
P5-T29 legacy-removal design tracking notes.

- `mvn -pl :fe-core -am test-compile` (main + test) passes; checkstyle 0
violations;
  connector import-gate (`tools/check-connector-imports.sh`) passes.
- `grep -rn org.apache.paimon fe/fe-core/src` → empty (main and test).
- `mvn -pl :fe-core dependency:tree -Dincludes=org.apache.paimon` →
empty (no paimon dep,
  direct or transitive).
- End-to-end paimon regression (`external_table_p0/paimon`, docker
`enablePaimonTest=true`)
  passed on this branch.

@924060929 924060929 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.

Part of a systematic post-merge review of the catalog-SPI series (#65185), verified against branch tip 3ba75b7 and legacy code on current master. Positive note first: GSON/persistence compat, sys tables, SHOW PARTITIONS columns and property passthrough all checked out clean in a separate removal audit, and three scary-looking predicate-converter candidates were REFUTED during adversarial verification because supportsCastPredicatePushdown()=false strips any cast-bearing conjunct before conversion — that guard is load-bearing (the value.toString() / Number-truncation arms in PaimonPredicateConverter are only safe because of it), so consider pinning it with a test/comment so a future 'enable cast pushdown' change doesn't silently unleash those arms.

Findings that anchor outside this PR's hunks:

  1. convertAnd partial-conjunction divergence (PaimonPredicateConverter.java:121): it keeps a partial conjunction of the convertible children, while legacy required BOTH sides of a nested AND to convert (master PaimonPredicateConverter.java:75-80 returns null otherwise). Verified sound — a partial AND is strictly weaker, convertOr rejects any unconvertible disjunct, and BE re-applies the full predicate — so it only changes which files are pruned (plan/profile-visible, strictly better). Worth a comment marking the divergence deliberate so a future parity audit doesn't re-flag it.

  2. selectedPartitionNum semantics changed (PluginDrivenScanNode): it now reports the Nereids FE-pruned partition count, while legacy paimon reported distinct partitions among SDK-planned splits (master PaimonScanNode.java:502). Both EXPLAIN partition=N/M and the sql_block_rule partition_num check (StmtExecutor passes scanNode.getSelectedPartitionNum() to SqlBlockRuleMgr for FileScanNodes) observe this — a rule that passed on legacy (limit 5; SDK touches 3 of 10 FE-selected partitions) now blocks the query.

  3. Shared infra placed in the connector: buildHadoopConfiguration/assembleHiveConf (storage-config assembly + classloader pinning) live as statics inside PaimonCatalogFactory; the iceberg migration already copy-pasted them (IcebergCatalogFactory javadoc literally says 'mirror PaimonCatalogFactory'). Every classloader/auth fix now needs manual re-application per connector — fe-connector-metastore-spi looks like the natural shared home.

// their built-in ExternalCatalog implementations until their ConnectorProviders are fully ready.
private static final Set<String> SPI_READY_TYPES =
ImmutableSet.of("jdbc", "es", "trino-connector", "max_compute");
ImmutableSet.of("jdbc", "es", "trino-connector", "max_compute", "paimon");

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.

Adding paimon here is the line that flips paimon EXPLAIN output from VPAIMON_SCAN_NODE to VPluginDrivenScanNode (verified live on this branch; master PaimonScanNode.java:159 passes "PAIMON_SCAN_NODE"). The tracking issue's invariant #3 explicitly lists EXPLAIN output as preserved, and plan-doc/deviations-log has no entry for the header rename (the fix-e work restored the explain body lines byte-for-byte but accepted the renamed header). If the unified name is intentional, suggest recording it as a deviation on #65185; if not, planNodeName = connectorType.toUpperCase() + "_SCAN_NODE" in PluginDrivenScanNode's ctor preserves the legacy header for every migrated connector at one stroke.

// fileDesc.setFileFormat(getFileFormat(...)).
return new PaimonScanRange.Builder()
.fileFormat("jni")
.fileFormat(defaultFileFormat)

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.

file_format here comes from the table-level file.format option (default parquet), while legacy derived it from the first data file's suffix with the option only as fallback (master PaimonScanNode.java:259 + PaimonSplit.java:59) — and the native path in this file still does suffix derivation. For a table whose files are ORC but whose options omit file.format (or whose format changed after writes), JNI/COUNT ranges now claim parquet. The only consumer appears to be paimon_cpp_reader's FILE_FORMAT/MANIFEST_FORMAT backfill, so this needs either an e2e check with an option-unset ORC table on the paimon-cpp path, or simply restoring suffix derivation for these two builders to match the native path.


if (supportNativeReader(optRawFiles)) {
// Native reader path
if (shouldUseNativeReader(paimonHandle.isForceJni(),

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.

The ignore_split_type session variable (IGNORE_JNI / IGNORE_NATIVE debug filtering) is not consumed anywhere on the plugin path — legacy honored it at four points in PaimonScanNode.getSplits (master :368-369, :389, :431, :471), and the variable still exists in SessionVariable at tip, so SET ignore_split_type=ignore_jni now silently no-ops for paimon. Either wire it into this routing loop or deprecate the variable; a silently dead debug knob is the worst of both.

case "VARIANT":
return new VariantType();
case "ARRAY":
return new ArrayType(toPaimonType(type.getChildren().get(0)));

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.

The to-Paimon (CREATE TABLE) direction drops nested nullability and struct-field comments: ARRAY element / MAP value / ROW fields get default nullability and no comment, while legacy DorisToPaimonTypeVisitor copied containsNull per element/value/field and carried StructField comments (master :57-78). Notably this is not an SPI limitation — ConnectorColumnConverter carries childrenNullable + childrenComments and IcebergSchemaBuilder consumes them; this connector just never reads them. Tables created through Doris now get a different paimon schema than legacy produced (visible to Flink/Spark and SHOW CREATE).

return Collections.emptyList();
}
List<String> keys = new ArrayList<>(spec.getFields().size());
for (ConnectorPartitionField field : spec.getFields()) {

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.

spec.getInitialValues() is never read, so CREATE TABLE ... PARTITION BY LIST (col) (PARTITION p VALUES IN ('x')) silently accepts and drops the explicit partition value definitions (verified live on this branch — table created, definitions gone). Legacy also dropped them, so it's not a regression, but the SPI now models the values (ConnectorPartitionValueDef / getInitialValues, javadoc: 'only meaningful for LIST/RANGE') while the only producer hardcodes Collections.emptyList() — dead API surface plus DDL accepted that cannot be honored. Since this builder already fail-fasts on unsupported transforms a few lines below, rejecting non-empty initial values with a clear error would be consistent and honest.

@@ -938,6 +933,8 @@ private static String pluginCatalogTypeToEngine(PluginDrivenExternalCatalog cata
switch (catalog.getType()) {
case "max_compute":
return ENGINE_MAXCOMPUTE;
case "paimon":

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.

This is now the third fe-core string switch that must be hand-edited per connector (with PluginDrivenExternalTable.getEngine() and getEngineTableTypeName()); the javadoc itself says they 'must stay in sync'. P6 already added its iceberg cases to all three, confirming the N-connectors × N-edits trajectory. Connector identity (legacy engine name / table-type name) looks like it belongs on the Connector SPI (e.g. Connector.getLegacyEngineName()), declared once per plugin — otherwise every future connector risks the exact silently-wrong-engine-name regression these switches exist to prevent, with no compile-time guard tying them together.

morningman added a commit that referenced this pull request Jul 10, 2026
…) (#64446)

Migrates the legacy in-tree Paimon catalog (fe-core `datasource/paimon`,
`metacache/paimon`, `property/metastore/*Paimon*` and their reverse
references) onto the catalog SPI as a self-contained
`fe-connector-paimon`
plugin, following the maxcompute full-adopter + cutover template.
`paimon`
is added to `SPI_READY_TYPES`, so a Paimon catalog now deserializes to
`PluginDrivenExternalCatalog` and all five functional areas —
normal-table
read, system-table read, DDL, MTMV/MVCC/time-travel (procedures are a
doc-only no-op: fe-core has none) — flow through the generic
`PluginDriven*`
bridge with no Paimon-specific branch in fe-core.

Paimon is the first lakehouse full-adopter to exercise the MVCC /
sys-table /
MTMV SPI surfaces, which become the reusable template for the upcoming
iceberg/hudi migrations, so the cutover is held to byte-parity with the
legacy path. The legacy `datasource/paimon/*` classes are left in place
(now
dead on the cutover path) and removed in a follow-up, mirroring the
maxcompute cutover -> P4 removal.

DefaultConnectorContext)

Source-agnostic seams so any connector can express these capabilities;
every
new method is a default no-op/identity, so hive/iceberg/jdbc/maxcompute/
trino/es are byte-for-byte unaffected.

- MVCC/time-travel: `resolveTimeTravel(session, handle,
ConnectorTimeTravelSpec)`
+ `applySnapshot(...)` (threads a pinned snapshot into the handle before
planScan); new immutable `ConnectorTimeTravelSpec` (SNAPSHOT_ID /
TIMESTAMP /
TAG / BRANCH / INCREMENTAL) that fe-core extracts from SQL;
`ConnectorMvccSnapshot`
  carries `schemaId` for schema-evolution-aware time travel.
- System tables: `listSupportedSysTables` / `getSysTableHandle`; fe-core
composes
  the `{base}${sys}` reference name.
- Scan: proportional split-weight
(`getSelfSplitWeight`/`getTargetSplitSize`),
COUNT(*) pushdown (`getPushDownRowCount`, 7-arg `planScan`), native-read
accounting (`isNativeReadRange`), `getDeleteFiles` (VERBOSE
merge-on-read),
`ignorePartitionPruneShortCircuit` (predicate-driven connectors keep
genuine-null
  partitions).
- Metadata: `ConnectorPartitionInfo.fileCount` +
`SUPPORTS_PARTITION_STATS` (rich
SHOW PARTITIONS); `ConnectorColumn.withTimeZone` (WITH_TIMEZONE DESC
marker,
  independent of the timestamp_tz mapping flag); connector cache control
  (`invalidateTable`/`invalidateAll`, `schemaCacheTtlSecondOverride`).
- `ConnectorContext`: six engine-side hooks the connector must not
implement itself
(`loadHiveConfResources`, `vendStorageCredentials`,
`getBackendStorageProperties`,
`normalizeStorageUri` x2, `getStorageProperties`), bound in
`DefaultConnectorContext`
over fe-core's single-source-of-truth credential/URI/HiveConf utilities
(no
  re-ported logic that could drift).

The metastore-side counterpart of the fe-filesystem StorageProperties
SPI: a
Paimon catalog resolves its backend (hive/dlf/rest/jdbc/filesystem) by
ServiceLoader dispatch on `paimon.catalog.type` instead of a fe-core
switch.

- `fe-connector-metastore-api`: hadoop/SDK-free `MetaStoreProperties`
contracts
  (HMS/DLF/REST/JDBC/FileSystem) carrying only Map/scalar facts.
- `fe-connector-metastore-spi`: `MetaStoreProvider<P>` + first-hit
`MetaStoreProviders`
dispatcher + five impls, registered via META-INF/services.
`HmsMetaStorePropertiesImpl`
reproduces the legacy `buildHmsHiveConf` key set and parity-critical
ordering
(storage overlay before the kerberos block; username/sasl last). Adding
a backend =
  one provider class + one services line (no central enum/switch).
- ServiceLoader is loaded with the SPI interface's own (plugin)
classloader, NOT the
thread-context CL: at CREATE CATALOG the static init first fires on an
FE worker
thread whose TCCL is the FE app loader, so a 1-arg load would cache an
empty
  provider list process-wide.
- fe-core bridges add
`initExecutionAuthenticator(List<StorageProperties>)` so
HDFS-Kerberos doAs is wired on the plugin path (legacy
`initializeCatalog` is dead).

Full read + DDL + partitions + statistics + system tables +
MVCC/time-travel across
all five metastore flavors, planning native (ORC/Parquet) vs JNI reads.

- `PaimonCatalogFactory` (pure flavor switch: Options + Hadoop
Configuration + HiveConf,
classloader-pinned for child-first loading), `PaimonCatalogOps`
(injection seam over
the remote Catalog for offline-testable metadata),
`PaimonConnectorMetadata` (~9 -> 28
methods: schema latest/at-snapshot, sys tables, MVCC/time-travel, DDL,
partitions,
  statistics, table descriptor).
- `PaimonScanPlanProvider` (~6 -> 30 methods): predicate/projection
pushdown,
native-vs-JNI routing, large-file sub-splitting with deletion vectors,
COUNT(*)
collapse, schema-evolution field-id dictionary, `$ro` unwrap to base
FileStoreTable.
- New: `PaimonSchemaBuilder` (CREATE TABLE),
`PaimonIncrementalScanParams` (@incr),
`PaimonTableResolver` (sys/branch-aware handle -> Table),
`PaimonLatestSnapshotCache`
+ `PaimonSchemaAtMemo` (per-catalog caches on the long-lived connector).
- `PaimonTypeMapping` gains the write direction (Doris -> Paimon) for
CREATE TABLE.

- NEW `PluginDrivenMvccExternalTable` (generic MvccTable + MTMV host:
the runtime class
for paimon tables), `PluginDrivenMvccSnapshot`,
`PluginDrivenSysExternalTable` +
`systable/PluginDrivenSysTable`. Behavior is selected by
`ConnectorCapability`
(SUPPORTS_MVCC_SNAPSHOT, SUPPORTS_PARTITION_STATS) — no paimon branch in
fe-core.
- `PluginDrivenScanNode`: MVCC pin threaded at every handle-consumption
site, sys-table
time-travel guard, COUNT pushdown, connector-agnostic EXPLAIN
re-emission.
- Cutover: `CatalogFactory` adds `paimon` to `SPI_READY_TYPES` and drops
the built-in
case; `GsonUtils` removes the 7 built-in `Paimon*` subtype registrations
and adds
replay compat so persisted `PaimonExternalCatalog` (all 5 flavors) /
`...Database`
-> `PluginDriven*` and `PaimonExternalTable` ->
`PluginDrivenMvccExternalTable` on
  deserialization (existing clusters upgrade without losing catalogs).
- Nereids/DDL: `PhysicalPlanTranslator` drops the `PaimonScanNode`
branch;
`UserAuthentication`, `CreateTableInfo`, `ShowPartitionsCommand`
generalized;
`Env.getDdlStmt` renders LOCATION/PROPERTIES for SHOW CREATE (paimon
engine, no
  credential leak).

- fe-filesystem: NEW `HdfsFileSystemProperties` / `HdfsConfigFileLoader`
(typed HDFS;
defaults-laden BE map vs defaults-free FE Hadoop-config map so it cannot
clobber a
co-bound object store's fs.s3a.* during a multi-backend merge); S3 MinIO
support +
  legacy tuning defaults; OSS/OBS/COS ANONYMOUS provider-type; new
`FileSystemFactory.bindAllStorageProperties` /
`FileSystemPluginManager.bindAll`.
- NEW `fe-kerberos` leaf module: dependency-free `AuthType` /
`KerberosAuthSpec` facts
  shared across modules without pulling in Hadoop.
- Packaging: NEW `fe-connector-paimon-hive-shade` relocates
`org.apache.thrift` for
HMS 2.3.7; the paimon plugin-zip excludes fe-thrift/libthrift (provided
parent-first)
to avoid a split `TBase` across loaders; self-contained bundle
(hadoop-aws, AWS-SDK,
hadoop-hdfs-client). `paimon.version` (1.3.1) pinned as the single
property across
FE connector / BE paimon-scanner / preload-extensions for FE<->BE
Table/Split serde.
- BE `PaimonJniScanner.getPredicates()` null-predicate backstop
(no-filter scan; pairs
  with the FE producer now always emitting an empty predicate list).

Parity fixes surfaced by the SPI cutover (each restores legacy
semantics):

- Storage/credentials: canonical `s3.*`/`oss.*` keys ->
`fs.s3a.*`/`fs.oss.*`
(STORAGE-CREDS); canonical `AWS_*` BE creds (STATIC-CREDS-BE); per-table
vended
DLF/STS token (REST-VENDED); `oss/cos/obs/s3a` -> `s3://` for data +
deletion-vector
paths (URI-NORMALIZE); external `hive.conf.resources` hive-site.xml
(HMS-CONFRES);
  real doAs on filesystem/jdbc + wrapped HMS read RPCs (KERBEROS-DOAS).
- Reads/types: paimon columns forced nullable (READ-NOTNULL);
`VARCHAR(65533)` not
widened to STRING (VARCHAR-BOUNDARY); dotted `enable.mapping.varbinary`
/
`.timestamp_tz` keys (MAPPING-FLAG-KEYS); per-type partition-value
rendering
(NATIVE-PARTVAL); genuine `\N` not coerced to NULL
(PARTITION-NULL-SENTINEL);
field-id history dict for rename-safe native reads (SCHEMA-EVOLUTION);
paimon-native
  split encode under `enable_paimon_cpp_reader` (CPP-READER).
- Planning/perf: restore the `force_jni_scanner` escape hatch
(FORCE-JNI-SCANNER);
intra-file native sub-splitting (NATIVE-SUBSPLIT); precomputed COUNT(*)
row count
(COUNT-PUSHDOWN); table row count for CBO / SHOW TABLE STATUS
(TABLE-STATS).
- Time travel / DDL: `FOR TIME AS OF` under CST/PST session zones
(TZ-ALIAS); resolved
+ validated `driver_url` for jdbc catalogs (JDBC-DRIVER-URL); reject a
local-only
  name conflict on CREATE TABLE (CREATE-TABLE-LOCAL-CONFLICT).

plan-doc P5 design / task-list / HANDOFF / deviation tracking notes.

- ~70 new FE unit suites: paimon connector (offline recording fakes:
`RecordingPaimonCatalogOps` / `FakePaimonTable`), fe-core PluginDriven*
MVCC /
sys-table / scan-node / split-weight, metastore-spi dispatch +
per-backend
  properties, fe-filesystem HDFS/MinIO/anonymous, fe-kerberos.
- checkstyle 0 violations; connector import-gate passes; FE main+test
compile clean.
- End-to-end paimon behavior is docker-gated (`enablePaimonTest=true`);
the five
functional areas are covered by the `external_table_p0/paimon`
regression suites,
  green on the latest CI run for this branch.
morningman added a commit that referenced this pull request Jul 10, 2026
… make fe-core paimon-SDK-free (T29) (#64653)

Follow-up to #64446 (the Paimon catalog-SPI cutover). After the cutover
a `paimon`
catalog deserializes to `PluginDrivenExternalCatalog` and its tables to
`PluginDrivenMvccExternalTable`, so no legacy `Paimon*` catalog / table
/ scan-node
object is ever instantiated — the entire legacy Paimon subsystem in
fe-core is dead
code. This PR removes it and makes fe-core's dependency tree fully
paimon-SDK-free
(zero `org.apache.paimon` imports, the 5 paimon maven deps dropped).
Mirrors the P4
maxcompute cutover → legacy-removal (#64256).

**1. Remove legacy subsystem + sever reverse-refs** (`7632a074e4b`)
- Delete the dead fe-core paimon files: `datasource/paimon/*` (all 5
flavor catalogs,
`PaimonExternalTable`, `PaimonMetadataOps`, `PaimonUtil`,
`source/PaimonScanNode` /
`PaimonSplit` / `PaimonPredicateConverter` / `PaimonSource` /
`PaimonValueConverter`,
`profile/*`), `datasource/metacache/paimon/*`,
`systable/PaimonSysTable`, and the
  legacy-only tests.
- Sever the live reverse-references to the deleted classes, keeping
every `PluginDriven`
/ connector sibling branch and the image/replay keep-set: drop the
`PAIMON` db-creation
switch arm in `ExternalCatalog`; the paimon engine routing in
`ExternalMetaCacheMgr` /
`ExternalMetaCacheRouteResolver`; the dead `PAIMON_EXTERNAL_TABLE`
branch in
`Env.getDdlStmt` (keep the LIVE `PLUGIN_EXTERNAL_TABLE` SHOW CREATE
LOCATION/PROPERTIES
rendering); the `PaimonSysExternalTable` branch in `UserAuthentication`;
the legacy
clauses + `handleShowPaimonTablePartitions()` in `ShowPartitionsCommand`
(keep
`hasPartitionStatsCapability()` and the
`TableType.PAIMON_EXTERNAL_TABLE` enum).
Landed as one compiling unit (mirrors the P4 #64300 precedent:
`PaimonUtils` still
  called the removed `ExternalMetaCacheMgr.paimon()`).
- Inline the `getPaimonCatalogType()` string literals
(`hms`/`filesystem`/`dlf`/`rest`/
`jdbc`) to decouple the still-consumed thin metastore-property classes
from the deleted
  `PaimonExternalCatalog`.

**2. Make fe-core paimon-SDK-free** (`32de7d16702`)
- Strip the dead paimon-SDK catalog-building cluster from
`AbstractPaimonProperties` + the
5 flavors (`initializeCatalog` / `buildCatalogOptions` /
`appendCatalogOptions` /
`getMetastoreType` / `normalizeS3Config` / … — 0 live callers; the
plugin path builds
catalogs connector-side). Keep every LIVE SDK-free duty: `warehouse`
`@ConnectorProperty`,
the Kerberos `executionAuthenticator` doAs wiring (read by
`PluginDrivenExternalCatalog`),
property normalization/validation, `getPaimonCatalogType`,
`Type.PAIMON`.
- Vended credentials: delete `PaimonVendedCredentialsProvider` + its
test + the
`VendedCredentialsFactory` `case PAIMON`. Its SDK methods were dead —
reachable only via
the iceberg-only `getStoragePropertiesMapWithVendedCredentials`; the
real paimon vended
path is the connector's `PaimonScanPlanProvider.extractVendedToken`
(moved off fe-core at
cutover). Relocate its one LIVE duty (the REST "skip static storage map"
gate) to a new
SDK-free `MetastoreProperties.isVendedCredentialsEnabled()` (base=false,
`PaimonRestMetaStoreProperties`=true); `CatalogProperty` routes iceberg
through its
provider (byte-identical) and everything else through the
metastore-props method.
- pom: drop `paimon-core` / `-common` / `-format` / `-s3` / `-jindo`
from `fe-core/pom.xml`.
`fe/pom.xml` `paimon.version` is kept (fe-connector-paimon + BE
paimon-scanner still
  consume it).

**3. Doc-sync** (`58160cbcb56`, `895e214b2bd`) — plan-doc HANDOFF /
PROGRESS / connectors /
P5-T29 legacy-removal design tracking notes.

- `mvn -pl :fe-core -am test-compile` (main + test) passes; checkstyle 0
violations;
  connector import-gate (`tools/check-connector-imports.sh`) passes.
- `grep -rn org.apache.paimon fe/fe-core/src` → empty (main and test).
- `mvn -pl :fe-core dependency:tree -Dincludes=org.apache.paimon` →
empty (no paimon dep,
  direct or transitive).
- End-to-end paimon regression (`external_table_p0/paimon`, docker
`enablePaimonTest=true`)
  passed on this branch.
morningman added a commit that referenced this pull request Jul 10, 2026
…) (#64446)

Migrates the legacy in-tree Paimon catalog (fe-core `datasource/paimon`,
`metacache/paimon`, `property/metastore/*Paimon*` and their reverse
references) onto the catalog SPI as a self-contained
`fe-connector-paimon`
plugin, following the maxcompute full-adopter + cutover template.
`paimon`
is added to `SPI_READY_TYPES`, so a Paimon catalog now deserializes to
`PluginDrivenExternalCatalog` and all five functional areas —
normal-table
read, system-table read, DDL, MTMV/MVCC/time-travel (procedures are a
doc-only no-op: fe-core has none) — flow through the generic
`PluginDriven*`
bridge with no Paimon-specific branch in fe-core.

Paimon is the first lakehouse full-adopter to exercise the MVCC /
sys-table /
MTMV SPI surfaces, which become the reusable template for the upcoming
iceberg/hudi migrations, so the cutover is held to byte-parity with the
legacy path. The legacy `datasource/paimon/*` classes are left in place
(now
dead on the cutover path) and removed in a follow-up, mirroring the
maxcompute cutover -> P4 removal.

DefaultConnectorContext)

Source-agnostic seams so any connector can express these capabilities;
every
new method is a default no-op/identity, so hive/iceberg/jdbc/maxcompute/
trino/es are byte-for-byte unaffected.

- MVCC/time-travel: `resolveTimeTravel(session, handle,
ConnectorTimeTravelSpec)`
+ `applySnapshot(...)` (threads a pinned snapshot into the handle before
planScan); new immutable `ConnectorTimeTravelSpec` (SNAPSHOT_ID /
TIMESTAMP /
TAG / BRANCH / INCREMENTAL) that fe-core extracts from SQL;
`ConnectorMvccSnapshot`
  carries `schemaId` for schema-evolution-aware time travel.
- System tables: `listSupportedSysTables` / `getSysTableHandle`; fe-core
composes
  the `{base}${sys}` reference name.
- Scan: proportional split-weight
(`getSelfSplitWeight`/`getTargetSplitSize`),
COUNT(*) pushdown (`getPushDownRowCount`, 7-arg `planScan`), native-read
accounting (`isNativeReadRange`), `getDeleteFiles` (VERBOSE
merge-on-read),
`ignorePartitionPruneShortCircuit` (predicate-driven connectors keep
genuine-null
  partitions).
- Metadata: `ConnectorPartitionInfo.fileCount` +
`SUPPORTS_PARTITION_STATS` (rich
SHOW PARTITIONS); `ConnectorColumn.withTimeZone` (WITH_TIMEZONE DESC
marker,
  independent of the timestamp_tz mapping flag); connector cache control
  (`invalidateTable`/`invalidateAll`, `schemaCacheTtlSecondOverride`).
- `ConnectorContext`: six engine-side hooks the connector must not
implement itself
(`loadHiveConfResources`, `vendStorageCredentials`,
`getBackendStorageProperties`,
`normalizeStorageUri` x2, `getStorageProperties`), bound in
`DefaultConnectorContext`
over fe-core's single-source-of-truth credential/URI/HiveConf utilities
(no
  re-ported logic that could drift).

The metastore-side counterpart of the fe-filesystem StorageProperties
SPI: a
Paimon catalog resolves its backend (hive/dlf/rest/jdbc/filesystem) by
ServiceLoader dispatch on `paimon.catalog.type` instead of a fe-core
switch.

- `fe-connector-metastore-api`: hadoop/SDK-free `MetaStoreProperties`
contracts
  (HMS/DLF/REST/JDBC/FileSystem) carrying only Map/scalar facts.
- `fe-connector-metastore-spi`: `MetaStoreProvider<P>` + first-hit
`MetaStoreProviders`
dispatcher + five impls, registered via META-INF/services.
`HmsMetaStorePropertiesImpl`
reproduces the legacy `buildHmsHiveConf` key set and parity-critical
ordering
(storage overlay before the kerberos block; username/sasl last). Adding
a backend =
  one provider class + one services line (no central enum/switch).
- ServiceLoader is loaded with the SPI interface's own (plugin)
classloader, NOT the
thread-context CL: at CREATE CATALOG the static init first fires on an
FE worker
thread whose TCCL is the FE app loader, so a 1-arg load would cache an
empty
  provider list process-wide.
- fe-core bridges add
`initExecutionAuthenticator(List<StorageProperties>)` so
HDFS-Kerberos doAs is wired on the plugin path (legacy
`initializeCatalog` is dead).

Full read + DDL + partitions + statistics + system tables +
MVCC/time-travel across
all five metastore flavors, planning native (ORC/Parquet) vs JNI reads.

- `PaimonCatalogFactory` (pure flavor switch: Options + Hadoop
Configuration + HiveConf,
classloader-pinned for child-first loading), `PaimonCatalogOps`
(injection seam over
the remote Catalog for offline-testable metadata),
`PaimonConnectorMetadata` (~9 -> 28
methods: schema latest/at-snapshot, sys tables, MVCC/time-travel, DDL,
partitions,
  statistics, table descriptor).
- `PaimonScanPlanProvider` (~6 -> 30 methods): predicate/projection
pushdown,
native-vs-JNI routing, large-file sub-splitting with deletion vectors,
COUNT(*)
collapse, schema-evolution field-id dictionary, `$ro` unwrap to base
FileStoreTable.
- New: `PaimonSchemaBuilder` (CREATE TABLE),
`PaimonIncrementalScanParams` (@incr),
`PaimonTableResolver` (sys/branch-aware handle -> Table),
`PaimonLatestSnapshotCache`
+ `PaimonSchemaAtMemo` (per-catalog caches on the long-lived connector).
- `PaimonTypeMapping` gains the write direction (Doris -> Paimon) for
CREATE TABLE.

- NEW `PluginDrivenMvccExternalTable` (generic MvccTable + MTMV host:
the runtime class
for paimon tables), `PluginDrivenMvccSnapshot`,
`PluginDrivenSysExternalTable` +
`systable/PluginDrivenSysTable`. Behavior is selected by
`ConnectorCapability`
(SUPPORTS_MVCC_SNAPSHOT, SUPPORTS_PARTITION_STATS) — no paimon branch in
fe-core.
- `PluginDrivenScanNode`: MVCC pin threaded at every handle-consumption
site, sys-table
time-travel guard, COUNT pushdown, connector-agnostic EXPLAIN
re-emission.
- Cutover: `CatalogFactory` adds `paimon` to `SPI_READY_TYPES` and drops
the built-in
case; `GsonUtils` removes the 7 built-in `Paimon*` subtype registrations
and adds
replay compat so persisted `PaimonExternalCatalog` (all 5 flavors) /
`...Database`
-> `PluginDriven*` and `PaimonExternalTable` ->
`PluginDrivenMvccExternalTable` on
  deserialization (existing clusters upgrade without losing catalogs).
- Nereids/DDL: `PhysicalPlanTranslator` drops the `PaimonScanNode`
branch;
`UserAuthentication`, `CreateTableInfo`, `ShowPartitionsCommand`
generalized;
`Env.getDdlStmt` renders LOCATION/PROPERTIES for SHOW CREATE (paimon
engine, no
  credential leak).

- fe-filesystem: NEW `HdfsFileSystemProperties` / `HdfsConfigFileLoader`
(typed HDFS;
defaults-laden BE map vs defaults-free FE Hadoop-config map so it cannot
clobber a
co-bound object store's fs.s3a.* during a multi-backend merge); S3 MinIO
support +
  legacy tuning defaults; OSS/OBS/COS ANONYMOUS provider-type; new
`FileSystemFactory.bindAllStorageProperties` /
`FileSystemPluginManager.bindAll`.
- NEW `fe-kerberos` leaf module: dependency-free `AuthType` /
`KerberosAuthSpec` facts
  shared across modules without pulling in Hadoop.
- Packaging: NEW `fe-connector-paimon-hive-shade` relocates
`org.apache.thrift` for
HMS 2.3.7; the paimon plugin-zip excludes fe-thrift/libthrift (provided
parent-first)
to avoid a split `TBase` across loaders; self-contained bundle
(hadoop-aws, AWS-SDK,
hadoop-hdfs-client). `paimon.version` (1.3.1) pinned as the single
property across
FE connector / BE paimon-scanner / preload-extensions for FE<->BE
Table/Split serde.
- BE `PaimonJniScanner.getPredicates()` null-predicate backstop
(no-filter scan; pairs
  with the FE producer now always emitting an empty predicate list).

Parity fixes surfaced by the SPI cutover (each restores legacy
semantics):

- Storage/credentials: canonical `s3.*`/`oss.*` keys ->
`fs.s3a.*`/`fs.oss.*`
(STORAGE-CREDS); canonical `AWS_*` BE creds (STATIC-CREDS-BE); per-table
vended
DLF/STS token (REST-VENDED); `oss/cos/obs/s3a` -> `s3://` for data +
deletion-vector
paths (URI-NORMALIZE); external `hive.conf.resources` hive-site.xml
(HMS-CONFRES);
  real doAs on filesystem/jdbc + wrapped HMS read RPCs (KERBEROS-DOAS).
- Reads/types: paimon columns forced nullable (READ-NOTNULL);
`VARCHAR(65533)` not
widened to STRING (VARCHAR-BOUNDARY); dotted `enable.mapping.varbinary`
/
`.timestamp_tz` keys (MAPPING-FLAG-KEYS); per-type partition-value
rendering
(NATIVE-PARTVAL); genuine `\N` not coerced to NULL
(PARTITION-NULL-SENTINEL);
field-id history dict for rename-safe native reads (SCHEMA-EVOLUTION);
paimon-native
  split encode under `enable_paimon_cpp_reader` (CPP-READER).
- Planning/perf: restore the `force_jni_scanner` escape hatch
(FORCE-JNI-SCANNER);
intra-file native sub-splitting (NATIVE-SUBSPLIT); precomputed COUNT(*)
row count
(COUNT-PUSHDOWN); table row count for CBO / SHOW TABLE STATUS
(TABLE-STATS).
- Time travel / DDL: `FOR TIME AS OF` under CST/PST session zones
(TZ-ALIAS); resolved
+ validated `driver_url` for jdbc catalogs (JDBC-DRIVER-URL); reject a
local-only
  name conflict on CREATE TABLE (CREATE-TABLE-LOCAL-CONFLICT).

plan-doc P5 design / task-list / HANDOFF / deviation tracking notes.

- ~70 new FE unit suites: paimon connector (offline recording fakes:
`RecordingPaimonCatalogOps` / `FakePaimonTable`), fe-core PluginDriven*
MVCC /
sys-table / scan-node / split-weight, metastore-spi dispatch +
per-backend
  properties, fe-filesystem HDFS/MinIO/anonymous, fe-kerberos.
- checkstyle 0 violations; connector import-gate passes; FE main+test
compile clean.
- End-to-end paimon behavior is docker-gated (`enablePaimonTest=true`);
the five
functional areas are covered by the `external_table_p0/paimon`
regression suites,
  green on the latest CI run for this branch.
morningman added a commit that referenced this pull request Jul 10, 2026
… make fe-core paimon-SDK-free (T29) (#64653)

Follow-up to #64446 (the Paimon catalog-SPI cutover). After the cutover
a `paimon`
catalog deserializes to `PluginDrivenExternalCatalog` and its tables to
`PluginDrivenMvccExternalTable`, so no legacy `Paimon*` catalog / table
/ scan-node
object is ever instantiated — the entire legacy Paimon subsystem in
fe-core is dead
code. This PR removes it and makes fe-core's dependency tree fully
paimon-SDK-free
(zero `org.apache.paimon` imports, the 5 paimon maven deps dropped).
Mirrors the P4
maxcompute cutover → legacy-removal (#64256).

**1. Remove legacy subsystem + sever reverse-refs** (`7632a074e4b`)
- Delete the dead fe-core paimon files: `datasource/paimon/*` (all 5
flavor catalogs,
`PaimonExternalTable`, `PaimonMetadataOps`, `PaimonUtil`,
`source/PaimonScanNode` /
`PaimonSplit` / `PaimonPredicateConverter` / `PaimonSource` /
`PaimonValueConverter`,
`profile/*`), `datasource/metacache/paimon/*`,
`systable/PaimonSysTable`, and the
  legacy-only tests.
- Sever the live reverse-references to the deleted classes, keeping
every `PluginDriven`
/ connector sibling branch and the image/replay keep-set: drop the
`PAIMON` db-creation
switch arm in `ExternalCatalog`; the paimon engine routing in
`ExternalMetaCacheMgr` /
`ExternalMetaCacheRouteResolver`; the dead `PAIMON_EXTERNAL_TABLE`
branch in
`Env.getDdlStmt` (keep the LIVE `PLUGIN_EXTERNAL_TABLE` SHOW CREATE
LOCATION/PROPERTIES
rendering); the `PaimonSysExternalTable` branch in `UserAuthentication`;
the legacy
clauses + `handleShowPaimonTablePartitions()` in `ShowPartitionsCommand`
(keep
`hasPartitionStatsCapability()` and the
`TableType.PAIMON_EXTERNAL_TABLE` enum).
Landed as one compiling unit (mirrors the P4 #64300 precedent:
`PaimonUtils` still
  called the removed `ExternalMetaCacheMgr.paimon()`).
- Inline the `getPaimonCatalogType()` string literals
(`hms`/`filesystem`/`dlf`/`rest`/
`jdbc`) to decouple the still-consumed thin metastore-property classes
from the deleted
  `PaimonExternalCatalog`.

**2. Make fe-core paimon-SDK-free** (`32de7d16702`)
- Strip the dead paimon-SDK catalog-building cluster from
`AbstractPaimonProperties` + the
5 flavors (`initializeCatalog` / `buildCatalogOptions` /
`appendCatalogOptions` /
`getMetastoreType` / `normalizeS3Config` / … — 0 live callers; the
plugin path builds
catalogs connector-side). Keep every LIVE SDK-free duty: `warehouse`
`@ConnectorProperty`,
the Kerberos `executionAuthenticator` doAs wiring (read by
`PluginDrivenExternalCatalog`),
property normalization/validation, `getPaimonCatalogType`,
`Type.PAIMON`.
- Vended credentials: delete `PaimonVendedCredentialsProvider` + its
test + the
`VendedCredentialsFactory` `case PAIMON`. Its SDK methods were dead —
reachable only via
the iceberg-only `getStoragePropertiesMapWithVendedCredentials`; the
real paimon vended
path is the connector's `PaimonScanPlanProvider.extractVendedToken`
(moved off fe-core at
cutover). Relocate its one LIVE duty (the REST "skip static storage map"
gate) to a new
SDK-free `MetastoreProperties.isVendedCredentialsEnabled()` (base=false,
`PaimonRestMetaStoreProperties`=true); `CatalogProperty` routes iceberg
through its
provider (byte-identical) and everything else through the
metastore-props method.
- pom: drop `paimon-core` / `-common` / `-format` / `-s3` / `-jindo`
from `fe-core/pom.xml`.
`fe/pom.xml` `paimon.version` is kept (fe-connector-paimon + BE
paimon-scanner still
  consume it).

**3. Doc-sync** (`58160cbcb56`, `895e214b2bd`) — plan-doc HANDOFF /
PROGRESS / connectors /
P5-T29 legacy-removal design tracking notes.

- `mvn -pl :fe-core -am test-compile` (main + test) passes; checkstyle 0
violations;
  connector import-gate (`tools/check-connector-imports.sh`) passes.
- `grep -rn org.apache.paimon fe/fe-core/src` → empty (main and test).
- `mvn -pl :fe-core dependency:tree -Dincludes=org.apache.paimon` →
empty (no paimon dep,
  direct or transitive).
- End-to-end paimon regression (`external_table_p0/paimon`, docker
`enablePaimonTest=true`)
  passed on this branch.
morningman added a commit that referenced this pull request Jul 10, 2026
…) (#64446)

Migrates the legacy in-tree Paimon catalog (fe-core `datasource/paimon`,
`metacache/paimon`, `property/metastore/*Paimon*` and their reverse
references) onto the catalog SPI as a self-contained
`fe-connector-paimon`
plugin, following the maxcompute full-adopter + cutover template.
`paimon`
is added to `SPI_READY_TYPES`, so a Paimon catalog now deserializes to
`PluginDrivenExternalCatalog` and all five functional areas —
normal-table
read, system-table read, DDL, MTMV/MVCC/time-travel (procedures are a
doc-only no-op: fe-core has none) — flow through the generic
`PluginDriven*`
bridge with no Paimon-specific branch in fe-core.

Paimon is the first lakehouse full-adopter to exercise the MVCC /
sys-table /
MTMV SPI surfaces, which become the reusable template for the upcoming
iceberg/hudi migrations, so the cutover is held to byte-parity with the
legacy path. The legacy `datasource/paimon/*` classes are left in place
(now
dead on the cutover path) and removed in a follow-up, mirroring the
maxcompute cutover -> P4 removal.

DefaultConnectorContext)

Source-agnostic seams so any connector can express these capabilities;
every
new method is a default no-op/identity, so hive/iceberg/jdbc/maxcompute/
trino/es are byte-for-byte unaffected.

- MVCC/time-travel: `resolveTimeTravel(session, handle,
ConnectorTimeTravelSpec)`
+ `applySnapshot(...)` (threads a pinned snapshot into the handle before
planScan); new immutable `ConnectorTimeTravelSpec` (SNAPSHOT_ID /
TIMESTAMP /
TAG / BRANCH / INCREMENTAL) that fe-core extracts from SQL;
`ConnectorMvccSnapshot`
  carries `schemaId` for schema-evolution-aware time travel.
- System tables: `listSupportedSysTables` / `getSysTableHandle`; fe-core
composes
  the `{base}${sys}` reference name.
- Scan: proportional split-weight
(`getSelfSplitWeight`/`getTargetSplitSize`),
COUNT(*) pushdown (`getPushDownRowCount`, 7-arg `planScan`), native-read
accounting (`isNativeReadRange`), `getDeleteFiles` (VERBOSE
merge-on-read),
`ignorePartitionPruneShortCircuit` (predicate-driven connectors keep
genuine-null
  partitions).
- Metadata: `ConnectorPartitionInfo.fileCount` +
`SUPPORTS_PARTITION_STATS` (rich
SHOW PARTITIONS); `ConnectorColumn.withTimeZone` (WITH_TIMEZONE DESC
marker,
  independent of the timestamp_tz mapping flag); connector cache control
  (`invalidateTable`/`invalidateAll`, `schemaCacheTtlSecondOverride`).
- `ConnectorContext`: six engine-side hooks the connector must not
implement itself
(`loadHiveConfResources`, `vendStorageCredentials`,
`getBackendStorageProperties`,
`normalizeStorageUri` x2, `getStorageProperties`), bound in
`DefaultConnectorContext`
over fe-core's single-source-of-truth credential/URI/HiveConf utilities
(no
  re-ported logic that could drift).

The metastore-side counterpart of the fe-filesystem StorageProperties
SPI: a
Paimon catalog resolves its backend (hive/dlf/rest/jdbc/filesystem) by
ServiceLoader dispatch on `paimon.catalog.type` instead of a fe-core
switch.

- `fe-connector-metastore-api`: hadoop/SDK-free `MetaStoreProperties`
contracts
  (HMS/DLF/REST/JDBC/FileSystem) carrying only Map/scalar facts.
- `fe-connector-metastore-spi`: `MetaStoreProvider<P>` + first-hit
`MetaStoreProviders`
dispatcher + five impls, registered via META-INF/services.
`HmsMetaStorePropertiesImpl`
reproduces the legacy `buildHmsHiveConf` key set and parity-critical
ordering
(storage overlay before the kerberos block; username/sasl last). Adding
a backend =
  one provider class + one services line (no central enum/switch).
- ServiceLoader is loaded with the SPI interface's own (plugin)
classloader, NOT the
thread-context CL: at CREATE CATALOG the static init first fires on an
FE worker
thread whose TCCL is the FE app loader, so a 1-arg load would cache an
empty
  provider list process-wide.
- fe-core bridges add
`initExecutionAuthenticator(List<StorageProperties>)` so
HDFS-Kerberos doAs is wired on the plugin path (legacy
`initializeCatalog` is dead).

Full read + DDL + partitions + statistics + system tables +
MVCC/time-travel across
all five metastore flavors, planning native (ORC/Parquet) vs JNI reads.

- `PaimonCatalogFactory` (pure flavor switch: Options + Hadoop
Configuration + HiveConf,
classloader-pinned for child-first loading), `PaimonCatalogOps`
(injection seam over
the remote Catalog for offline-testable metadata),
`PaimonConnectorMetadata` (~9 -> 28
methods: schema latest/at-snapshot, sys tables, MVCC/time-travel, DDL,
partitions,
  statistics, table descriptor).
- `PaimonScanPlanProvider` (~6 -> 30 methods): predicate/projection
pushdown,
native-vs-JNI routing, large-file sub-splitting with deletion vectors,
COUNT(*)
collapse, schema-evolution field-id dictionary, `$ro` unwrap to base
FileStoreTable.
- New: `PaimonSchemaBuilder` (CREATE TABLE),
`PaimonIncrementalScanParams` (@incr),
`PaimonTableResolver` (sys/branch-aware handle -> Table),
`PaimonLatestSnapshotCache`
+ `PaimonSchemaAtMemo` (per-catalog caches on the long-lived connector).
- `PaimonTypeMapping` gains the write direction (Doris -> Paimon) for
CREATE TABLE.

- NEW `PluginDrivenMvccExternalTable` (generic MvccTable + MTMV host:
the runtime class
for paimon tables), `PluginDrivenMvccSnapshot`,
`PluginDrivenSysExternalTable` +
`systable/PluginDrivenSysTable`. Behavior is selected by
`ConnectorCapability`
(SUPPORTS_MVCC_SNAPSHOT, SUPPORTS_PARTITION_STATS) — no paimon branch in
fe-core.
- `PluginDrivenScanNode`: MVCC pin threaded at every handle-consumption
site, sys-table
time-travel guard, COUNT pushdown, connector-agnostic EXPLAIN
re-emission.
- Cutover: `CatalogFactory` adds `paimon` to `SPI_READY_TYPES` and drops
the built-in
case; `GsonUtils` removes the 7 built-in `Paimon*` subtype registrations
and adds
replay compat so persisted `PaimonExternalCatalog` (all 5 flavors) /
`...Database`
-> `PluginDriven*` and `PaimonExternalTable` ->
`PluginDrivenMvccExternalTable` on
  deserialization (existing clusters upgrade without losing catalogs).
- Nereids/DDL: `PhysicalPlanTranslator` drops the `PaimonScanNode`
branch;
`UserAuthentication`, `CreateTableInfo`, `ShowPartitionsCommand`
generalized;
`Env.getDdlStmt` renders LOCATION/PROPERTIES for SHOW CREATE (paimon
engine, no
  credential leak).

- fe-filesystem: NEW `HdfsFileSystemProperties` / `HdfsConfigFileLoader`
(typed HDFS;
defaults-laden BE map vs defaults-free FE Hadoop-config map so it cannot
clobber a
co-bound object store's fs.s3a.* during a multi-backend merge); S3 MinIO
support +
  legacy tuning defaults; OSS/OBS/COS ANONYMOUS provider-type; new
`FileSystemFactory.bindAllStorageProperties` /
`FileSystemPluginManager.bindAll`.
- NEW `fe-kerberos` leaf module: dependency-free `AuthType` /
`KerberosAuthSpec` facts
  shared across modules without pulling in Hadoop.
- Packaging: NEW `fe-connector-paimon-hive-shade` relocates
`org.apache.thrift` for
HMS 2.3.7; the paimon plugin-zip excludes fe-thrift/libthrift (provided
parent-first)
to avoid a split `TBase` across loaders; self-contained bundle
(hadoop-aws, AWS-SDK,
hadoop-hdfs-client). `paimon.version` (1.3.1) pinned as the single
property across
FE connector / BE paimon-scanner / preload-extensions for FE<->BE
Table/Split serde.
- BE `PaimonJniScanner.getPredicates()` null-predicate backstop
(no-filter scan; pairs
  with the FE producer now always emitting an empty predicate list).

Parity fixes surfaced by the SPI cutover (each restores legacy
semantics):

- Storage/credentials: canonical `s3.*`/`oss.*` keys ->
`fs.s3a.*`/`fs.oss.*`
(STORAGE-CREDS); canonical `AWS_*` BE creds (STATIC-CREDS-BE); per-table
vended
DLF/STS token (REST-VENDED); `oss/cos/obs/s3a` -> `s3://` for data +
deletion-vector
paths (URI-NORMALIZE); external `hive.conf.resources` hive-site.xml
(HMS-CONFRES);
  real doAs on filesystem/jdbc + wrapped HMS read RPCs (KERBEROS-DOAS).
- Reads/types: paimon columns forced nullable (READ-NOTNULL);
`VARCHAR(65533)` not
widened to STRING (VARCHAR-BOUNDARY); dotted `enable.mapping.varbinary`
/
`.timestamp_tz` keys (MAPPING-FLAG-KEYS); per-type partition-value
rendering
(NATIVE-PARTVAL); genuine `\N` not coerced to NULL
(PARTITION-NULL-SENTINEL);
field-id history dict for rename-safe native reads (SCHEMA-EVOLUTION);
paimon-native
  split encode under `enable_paimon_cpp_reader` (CPP-READER).
- Planning/perf: restore the `force_jni_scanner` escape hatch
(FORCE-JNI-SCANNER);
intra-file native sub-splitting (NATIVE-SUBSPLIT); precomputed COUNT(*)
row count
(COUNT-PUSHDOWN); table row count for CBO / SHOW TABLE STATUS
(TABLE-STATS).
- Time travel / DDL: `FOR TIME AS OF` under CST/PST session zones
(TZ-ALIAS); resolved
+ validated `driver_url` for jdbc catalogs (JDBC-DRIVER-URL); reject a
local-only
  name conflict on CREATE TABLE (CREATE-TABLE-LOCAL-CONFLICT).

plan-doc P5 design / task-list / HANDOFF / deviation tracking notes.

- ~70 new FE unit suites: paimon connector (offline recording fakes:
`RecordingPaimonCatalogOps` / `FakePaimonTable`), fe-core PluginDriven*
MVCC /
sys-table / scan-node / split-weight, metastore-spi dispatch +
per-backend
  properties, fe-filesystem HDFS/MinIO/anonymous, fe-kerberos.
- checkstyle 0 violations; connector import-gate passes; FE main+test
compile clean.
- End-to-end paimon behavior is docker-gated (`enablePaimonTest=true`);
the five
functional areas are covered by the `external_table_p0/paimon`
regression suites,
  green on the latest CI run for this branch.
morningman added a commit that referenced this pull request Jul 10, 2026
… make fe-core paimon-SDK-free (T29) (#64653)

Follow-up to #64446 (the Paimon catalog-SPI cutover). After the cutover
a `paimon`
catalog deserializes to `PluginDrivenExternalCatalog` and its tables to
`PluginDrivenMvccExternalTable`, so no legacy `Paimon*` catalog / table
/ scan-node
object is ever instantiated — the entire legacy Paimon subsystem in
fe-core is dead
code. This PR removes it and makes fe-core's dependency tree fully
paimon-SDK-free
(zero `org.apache.paimon` imports, the 5 paimon maven deps dropped).
Mirrors the P4
maxcompute cutover → legacy-removal (#64256).

**1. Remove legacy subsystem + sever reverse-refs** (`7632a074e4b`)
- Delete the dead fe-core paimon files: `datasource/paimon/*` (all 5
flavor catalogs,
`PaimonExternalTable`, `PaimonMetadataOps`, `PaimonUtil`,
`source/PaimonScanNode` /
`PaimonSplit` / `PaimonPredicateConverter` / `PaimonSource` /
`PaimonValueConverter`,
`profile/*`), `datasource/metacache/paimon/*`,
`systable/PaimonSysTable`, and the
  legacy-only tests.
- Sever the live reverse-references to the deleted classes, keeping
every `PluginDriven`
/ connector sibling branch and the image/replay keep-set: drop the
`PAIMON` db-creation
switch arm in `ExternalCatalog`; the paimon engine routing in
`ExternalMetaCacheMgr` /
`ExternalMetaCacheRouteResolver`; the dead `PAIMON_EXTERNAL_TABLE`
branch in
`Env.getDdlStmt` (keep the LIVE `PLUGIN_EXTERNAL_TABLE` SHOW CREATE
LOCATION/PROPERTIES
rendering); the `PaimonSysExternalTable` branch in `UserAuthentication`;
the legacy
clauses + `handleShowPaimonTablePartitions()` in `ShowPartitionsCommand`
(keep
`hasPartitionStatsCapability()` and the
`TableType.PAIMON_EXTERNAL_TABLE` enum).
Landed as one compiling unit (mirrors the P4 #64300 precedent:
`PaimonUtils` still
  called the removed `ExternalMetaCacheMgr.paimon()`).
- Inline the `getPaimonCatalogType()` string literals
(`hms`/`filesystem`/`dlf`/`rest`/
`jdbc`) to decouple the still-consumed thin metastore-property classes
from the deleted
  `PaimonExternalCatalog`.

**2. Make fe-core paimon-SDK-free** (`32de7d16702`)
- Strip the dead paimon-SDK catalog-building cluster from
`AbstractPaimonProperties` + the
5 flavors (`initializeCatalog` / `buildCatalogOptions` /
`appendCatalogOptions` /
`getMetastoreType` / `normalizeS3Config` / … — 0 live callers; the
plugin path builds
catalogs connector-side). Keep every LIVE SDK-free duty: `warehouse`
`@ConnectorProperty`,
the Kerberos `executionAuthenticator` doAs wiring (read by
`PluginDrivenExternalCatalog`),
property normalization/validation, `getPaimonCatalogType`,
`Type.PAIMON`.
- Vended credentials: delete `PaimonVendedCredentialsProvider` + its
test + the
`VendedCredentialsFactory` `case PAIMON`. Its SDK methods were dead —
reachable only via
the iceberg-only `getStoragePropertiesMapWithVendedCredentials`; the
real paimon vended
path is the connector's `PaimonScanPlanProvider.extractVendedToken`
(moved off fe-core at
cutover). Relocate its one LIVE duty (the REST "skip static storage map"
gate) to a new
SDK-free `MetastoreProperties.isVendedCredentialsEnabled()` (base=false,
`PaimonRestMetaStoreProperties`=true); `CatalogProperty` routes iceberg
through its
provider (byte-identical) and everything else through the
metastore-props method.
- pom: drop `paimon-core` / `-common` / `-format` / `-s3` / `-jindo`
from `fe-core/pom.xml`.
`fe/pom.xml` `paimon.version` is kept (fe-connector-paimon + BE
paimon-scanner still
  consume it).

**3. Doc-sync** (`58160cbcb56`, `895e214b2bd`) — plan-doc HANDOFF /
PROGRESS / connectors /
P5-T29 legacy-removal design tracking notes.

- `mvn -pl :fe-core -am test-compile` (main + test) passes; checkstyle 0
violations;
  connector import-gate (`tools/check-connector-imports.sh`) passes.
- `grep -rn org.apache.paimon fe/fe-core/src` → empty (main and test).
- `mvn -pl :fe-core dependency:tree -Dincludes=org.apache.paimon` →
empty (no paimon dep,
  direct or transitive).
- End-to-end paimon regression (`external_table_p0/paimon`, docker
`enablePaimonTest=true`)
  passed on this branch.
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.

3 participants