Repository navigation
Conversation
…ff the removed privilege package Paimon 2.1 drops org.apache.paimon.privilege entirely (the only remaining DelegatedFileStoreTable subclass is FallbackReadFileStoreTable), so: - PaimonReaderOptions: reject unknown non-fallback planning delegates instead of peeling PrivilegedFileStoreTable; FallbackRead pairs keep being normalized branch-independently. Delegate traversal in the effective-table validation is unchanged. - Tests: replace PrivilegedFileStoreTable.wrap fixtures with a minimal test-only PlainDelegateFileStoreTable (fe-core and paimon-connector), keeping the delegate-peeling/rejection coverage. - PaimonJniScannerTest: the 2.1 SystemTableLoader consults coreOptions().queryAuthEnabled() for physical metadata tables, so the serializable-table proxy fixture now serves coreOptions(). Paimon 2.1 also reads variant columns zero-copy via ArrowBuf.nioBuffer() -> MemoryUtil.directBuffer, which reflects on the private java.nio.DirectByteBuffer(long, int) constructor and therefore needs --add-opens=java.base/java.nio on JDK 17; add the opens to the paimon-connector surefire argLine, mirroring fe-core. Also bumps doris.hive.catalog.shade.version to 3.1.4-SNAPSHOT. Verified: ./build.sh --fe green; fe-core *Paimon* tests green via run-fe-ut.sh; paimon-connector 98/98 green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Conflicts: fe/fe-core/src/test/java/org/apache/doris/datasource/paimon/PaimonExternalMetaCacheTest.java
Ports the internal PK-vector search (ann.patch, bleem ce52f81) onto the community paimon 2.1 java client and the paimon-rust baac87c eager Top-K C API, and wires it through the FileScannerV2 rust table reader. FE - New PushDownVectorTopNIntoPaimonScan rewrite: TopN[dis] -> Project [dis := dist_fn(vec, q)] -> [Filter] -> PaimonScan becomes an index-only ANN scan producing the reader-generated __paimon_search_score_to_dis column; every dist_fn occurrence is replaced by that slot. Gated on enable_paimon_rust_reader AND enable_file_scanner_v2 (the V1 scanner rejects PAIMON_RUST). AnnTopNInfo carries query vector / column / folded limit / TVectorMetric through the scan plumbing. - PaimonScanNode plans one BucketVectorSearchSplit per (partition, bucket) via newBatchVectorSearchBuilder and serializes it with Paimon's DataOutputSerializer (the community client no longer ships BucketVectorSearchSplitSerializer). The logical search request reuses the community TExternalSearchRequest (field 39 of TFileScanRangeParams); Paimon-only tuning travels in the new TPaimonVectorSearchOptions (field 5), filtered to field-/algorithm-scoped keys because primaryKeyVectorIndexOptions seeds itself with the whole table property bag (thrift-fatal nulls, credentials, redundancy). - PaimonUtil maps Paimon VECTOR to ARRAY<FLOAT> so the distance functions type-check; the JNI connector reads VECTOR columns via getVector. BE - PaimonRustTableReader gains a vector mode: the scan range carries a TPaimonVectorPayload (TPaimonFileDesc field 20) and the reader runs the eager Top-K chain paimon_bucket_vector_search_split_deserialize -> new_scan -> new_read -> plan_from_bucket_splits -> vector_read_read, then converts paimon-rust's __paimon_search_score into the metric-native distance in place while filling the block (L2: sqrt(1/score - 1) with a catastrophic-cancellation clamp and negative sentinels for corrupt scores; inner_product: identity). Deliberately not ported from the internal patch: Alluxio path rewriting (no community equivalent), the parquet nested lazy-read page-skip optimization, and the internal-only shade/pom version bumps. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 06e99cb) Port notes (rebased onto the merged PR apache#66227, which grew further after the ann branch forked at 8272fae): - PlanNodes.thrift: TFileScanRange field 39 is taken upstream by parquet_timestamp_semantics_version (40 by contains_native_parquet), so external_search_request is renumbered to field 41 on this branch. - PaimonScanNode: keep the upstream gate chain (restTokenTable, externalFileSplit, deduplicateIgnoreDelete, rustUnsupportedMergeOption, scan.mode default check, all-backend isPaimonRustReaderSupported, PaimonRustReaderCapabilities). A vector split bypasses only the per-split PaimonRustReaderCapabilities check: its whitelist describes the batch reader (single-file PK buckets, zero delete rows), while a vector bucket legitimately spans multiple files and applies deletion vectors inside the search itself; all table/session-level gates still apply and a vector split that fails them throws loudly as before. - paimon_rust_table_reader_test.cpp: keep both the upstream AdaptiveBatchSizeBoundsWideStringReads fixture test and the ported vector / score-transform tests. Conflicts: be/test/format_v2/table/paimon_rust_table_reader_test.cpp fe/fe-core/src/main/java/org/apache/doris/datasource/paimon/source/PaimonScanNode.java fe/fe-core/src/test/java/org/apache/doris/datasource/paimon/PaimonUtilTest.java gensrc/thrift/PlanNodes.thrift
Fix clang-format 16 compliance for the files touched by the PK-vector
(ANN) search port: include ordering and comment spacing in
paimon_rust_table_reader.{h,cpp}, and continuation alignment in
paimon_rust_table_reader_test.cpp.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 8f62540)
…package port 34731eb ported FE off org.apache.paimon.privilege but missed PaimonExternalTable and PaimonRowCountTest, both introduced by the upstream row-count backport 094b27b; and that backport was written against an older Paimon API: - PaimonExternalTable: drop the dead instanceof PrivilegedFileStoreTable branch in fetchRowCount (the wrapper no longer exists in 2.1; authorization stays on the queryAuthEnabled path) - PaimonRowCountTest: SchemaManager is an interface in 2.1, use FileSystemSchemaManager; port the 20-arg Snapshot constructor to the 22-arg 2.1 signature; drop testSelectOnlyPrivilegeWrapper along with the removed wrapper (coverage kept by testCatalogQueryAuthorizationWithoutPlanning) - PaimonScanNodeTest: add the missing closing brace of testRustReaderSelectionAcceptsAnonymousAndDefaultProviderModes, which broke checkstyle parsing of the file Verified: ./build.sh --fe green; PaimonRowCountTest, PaimonScanNodeTest and PaimonExternalTableTest 80/80 green via run-fe-ut.sh. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit ffa45cd)
Fix include ordering in paimon_rust_table_reader.cpp to keep clang-format 16 compliance. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit ab8f57f)
…port The PaimonExternalMetaCacheTest conflict resolution of the branch-4.1 privilege-package port (d96ce64) kept the two org.apache.paimon.privilege imports that Paimon 2.1 no longer ships. Nothing in the file references them anymore, so checkstyle fails the fe-core build (and compilation would fail on the missing classes). Same cleanup as ffa45cd did for the other missed files on the ann branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ests SchemaManager became an interface in Paimon 2.1 (same port the ann branch's ffa45cd did for PaimonRowCountTest); the two fixture builders that still call new SchemaManager(fileIO, path) fail fe-core test compilation. FileSystemSchemaManager keeps the same constructor and createTable behavior. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… the batch gate
A PK-vector search split is never read as a DataSplit: FE ships it serialized
inside vector_payload (paimon_split is empty on the wire) and BE executes
paimon-rust's eager Top-K chain, which applies deletion vectors and the merge
inside the search itself. The batch-reader gate nevertheless judged it by
DataSplit shape, so the standard PK-vector table layout (partial-update merge
engine with deletion vectors and uncompacted level-0 files -- the shape the
batch reader rightly rejects via the splitDvNotMaterialized gate) tripped the
vector branch's broken-invariant RuntimeException ("PK-vector search split
requires the paimon-rust reader...") and no ANN query could run on it.
Split the gate: the vector branch keeps every gate that constrains the search
itself (both session switches, !fallbackRead for the schema-json open,
provider-mode translation and auth shapes, the merge-option matrix and
merge-on-read shape, orc-ltz / projected-variant bounds on the returned user
columns, storage scheme and credential shape, scan-mode default, all-backend
rust capability) and drops only the DataSplit-shape gates that judge a split
the vector path never opens (nativeSplit, splitDvNotMaterialized,
externalFileSplit, the PaimonRustReaderCapabilities whitelist). The batch
branch is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…plits The PK merge-engine gate (primary-key partial-update / aggregation tables route to JNI) and the PK multi-file gate (dataFiles().size() > 1 routes to JNI) both exist for the pinned merge reader's unbounded losing-batch retention, which no delete count or batch size can bound. A rawConvertible split whose files are all compacted (level != 0) never reaches that merge reader: paimon-rust's read_pk dispatch (pk_split_needs_merge / is_fully_materialized_pk_dv) sends it to DataFileReader's raw path, which streams one file at a time, each bounded by read.batch-size, with the split's deletion-vector factory masking stale rows per file. Both gates now exempt such a split: - the merge-engine gate moves from the table-level tableCompatible to a per-split check (its rationale only applies to splits that must merge), - the multi-file gate gains the same readsRawWithoutMerge exemption. Per-file retract hygiene (deleteRowCount known zero) and the schema-history / evolution whitelist are unchanged: they bound the raw path too. Without this, a compacted PK bucket with multiple files -- the standard PK-vector table layout, and any compacted partial-update table -- could never use the rust reader for batch reads at all. Verified: PaimonScanNodeTest 96/96 (new testRustMultiFileRawConvertibleSplitStaysOnRust covers raw+compacted multi-file -> RUST, non-raw -> JNI, and level-0 + DV -> JNI). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ack reasons The PK-vector search gate and the batch reader gate judged the same splits with two parallel condition lists. They now share one gate chain (session switches, FallbackRead shape, incremental scans, provider-mode translation, query-auth / REST-token shapes, merge-option matrix, merge-on-read, orc-ltz / projected-variant bounds, storage scheme and credentials, scan-mode default, BE capability negotiation, FileStoreTable schema json); only the gates that judge the DataSplit the batch reader opens (native shape, unmaterialized PU/AGG deletion vectors, external files, the PaimonRustReaderCapabilities whitelist) are skipped for a PK-vector split, which is never read as a DataSplit -- FE ships it serialized inside vector_payload and BE executes paimon-rust's eager Top-K chain, which applies deletion vectors and the merge inside the search itself. Without this split, the standard PK-vector table layout (partial-update merge engine with deletion vectors and uncompacted level-0 files) tripped the vector branch's broken-invariant RuntimeException and no ANN query could run. The scan-mode check stays lazy (schema() may be null on fallback-shaped tables that other gates already rejected) and is now null-safe. Whenever the rust reader is enabled and a logical split still routes to JNI, the FE log now states the exact gate combination that rejected it, with the table location and the split's bucket and file count, one INFO line per distinct reason combination per scan node: paimon-rust reader enabled but a logical paimon split routed to JNI: table=... split=bucket=0 files=30 reasons=[split_dv_not_materialized] 18 reason tags cover every gate, with capability_whitelist as the catch-all when only the per-split whitelist rejected the split. Verified: run-fe-ut.sh --run PaimonScanNodeTest,PushDownVectorTopNIntoPaimonScanTest 115/115 green (1 known core-count skip); on the 9630 cluster the 2048-dim IVF vector query, the relaxed rust batch read and the fallback log line were each exercised end to end. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The PushDownVectorTopNIntoPaimonScan rewrite used to trigger on the two session flags alone. But the rewrite is a one-way gate: a PK-vector split has no JNI fallback (the JNI reader ignores the vector payload and would return a non-ANN scan with an empty distance column), so once rewritten the scan node can only fail loudly on a gate failure. A table whose rust gate would fail -- credential provider shape, REST-token IO, query-auth, unsupported merge options, deletion-vector merge-on-read, scan mode, unverified storage scheme or HDFS credentials, no rust-capable backend -- was turned from a slow-but-correct query into a hard error. Extract the table-, storage- and backend-level gates into PaimonRustEligibility (including the credential-provider translation predicate, providerModeTranslatable), computed once and shared by every site that must agree: the rewrite's pre-flight (via PaimonVectorSearch.vectorScanEligible) and PaimonScanNode's per-split gate, where the batch and PK-vector branches had previously each evaluated (and commented) the same predicate set separately. The gate-failing table is simply not rewritten, and the query still runs, computing the distance in Doris. An incremental scan (t@incr / BETWEEN) is likewise not rewritten: the VectorScan builder has no incremental mode, so a vector payload would silently return snapshot rows for a query that asked for changes. Keeping it un-rewritten runs the ordinary incremental scan with the distance computed in Doris. The scan node keeps !fallbackRead and !isIncremental on the vector branch as the authoritative backstops, and retains the split- and projection-dependent bounds (native DataSplit shape, unmaterialized deletion vectors, external-path files, capability whitelist, ORC LTZ schema, projected variant) the shared predicate cannot see. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The batch reader pushes the scanner's adaptive batch size as read.batch-size into the rust read builder. The PK-vector path cannot do the same: paimon-rust's PK vector readers construct their DataFileReaders without with_batch_size, so a read.batch-size option — from the schema, t@options or the vector search options — is silently ignored there and the path batches at the parquet-rust default. Document the known difference at the vector builder's construction site: the vector stages are single-column streams feeding bounded Top-K heaps and the output is capped by top_k, so wiring it needs a paimon-rust change, not a BE one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…2.1 SDK The Paimon 2.1 migration broke every s3-warehouse paimon catalog: the SDK's S3FileIO now reads its credentials exclusively from CatalogContext.options() (S3FileIO#configure -> loadHadoopConfigFromContext copies the s3./s3a./fs.s3a.-prefixed option entries and never consults the Hadoop configuration), while Doris assembles the credentials exactly there — in the conf built from StorageProperties and handed to CatalogContext.create(options, conf). The 1.4 FileIO read that conf, so the migration silently dropped the credential channel: catalog init fails on the first file operation with "SdkClientException: Unable to load credentials from system settings", and all four test_paimon_rust_reader_* regression suites fail at their first query (a catalog created on the 1.4 FE has the same failure after the upgrade). Mirror the fs.s3a.* entries Doris built into the catalog options at the three initializeCatalog sites (filesystem, HMS, JDBC metastores), so the 2.1 SDK sees the same credentials the Hadoop-compliant readers do. User extras (path style, connection limits) flow along; entries outside the prefix are not the SDK's concern. Verified locally: all four rust suites pass again and the new PK-vector ANN suite can read its minio fixture. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paimon 2.1 made aggregate merges fail loud on numeric overflow
("byte overflow: 127 + 1 = 128") where 1.4 wrapped silently, so two
compatibility cases that assert the wrapped value now fail on both
readers: the TINYINT sum/product integer cases and the TINYINT
default-aggregate-sum partial-update case. Assert the overflow failure
on both readers instead, and add non-overflow siblings (sum 10+20,
product 10*12 — in range for every case type including the TINYINT
product) so the merge value comparisons keep their coverage. The
decimal and max cases never overflow and stay unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The PK-vector (ANN) feature — the PushDownVectorTopNIntoPaimonScan rewrite, the shared rust eligibility gate and the BE paimon-rust vector reader — had no regression coverage: the ANN groovy suites all target internal-table faiss indexes, and the paimon suites have no vector tables at all. Add test_paimon_pk_vector_ann.groovy, run against a minio warehouse the way the rust reader suites are. The suite builds its own fixture through the Spark ThriftServer's paimon catalog (no Doris DDL can: the FE type mapping has no VECTOR column in either direction, and the ivf-rq index build needs the vindex factory jars — paimon-vector + paimon-vector-index-java — that only the Spark server carries; see regression.md for the 2.1 jar list): 512 deterministic 16-dim vectors derived from the row id (unique: mod 521, a prime above the row count), nlist = nprobe = 4 — exhaustive recall, so ANN must equal brute force by construction. Every ANN leg is compared against the exact brute-force answer the same SQL computes with the rust reader disabled: l2 top-5 and top-1 (the probe row itself, distance 0), a non-index-only projection with the tag asserted outright, a residual filter, the FileScannerV2-off no-rewrite leg, and inner_product desc. The ANN leg is also asserted to have actually executed on the paimon-rust vector reader via its profile, so the comparisons cannot pass on a silent brute-force scan. Near-tied neighbors can swap between the legs (f32 in the reader vs double in Doris), so the comparison checks top-1 exactly, the top-3 in order and the top-N as a set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Both failures were verified by hand-reproducing the queries against the
leftover fixture data: Doris resolved everything correctly and the suites'
own Spark-side assumptions stopped holding after the ThriftServer switch.
- test_paimon_schema_time_travel_matrix: the snapshotCommitTimeMillis
helper converted the paimon NTZ commit time through Spark's
convert_timezone + unix_millis, which the 2.1 ThriftServer shifts by
the session UTC offset - the value landed 8h in the future and every
scan.creation-time-millis leg silently resolved to the latest snapshot
("Unknown column 'old_name'"). Read the commit time through Doris
instead: unix_timestamp keeps the fractional seconds, so *1000 is the
exact commit instant and matches the creation-time resolution ("earliest
snapshot committed at or after the value") on the same clock.
- test_paimon_spark_doris_consistency_demo: Doris renders VARBINARY nested
in arrays/maps/structs as 0x-hex tokens while Spark's JDBC returns the
raw bytes, so identical nested binary values compared unequal across
engines. assertSparkDorisResultEquals now decodes the 0x tokens on both
sides after normalization - the substitution is a no-op on the raw
rendering, so equal stays equal for the iceberg suites sharing the
helper, and scalar binary columns (byte[] path) are unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
Both failures pinned a behavior that is still correct under the 2.1 SDK;
only the observable text/metadata changed:
- test_paimon_write_transaction FT-019: a row larger than the whole write
buffer is still rejected inside the paimon writer and nothing is
published, but the 2.1 SDK exception message gained a qualifier
("maximum size of a Paimon write sort buffer"). Match on the prefix
common to both SDKs so the assertion keeps its meaning.
- test_paimon_orc_ltz_encoding: the 1.4 connector materialized unset
property defaults into the stored table options, so the modern table
read back orc.timestamp-ltz.legacy.type=false; the 2.1 connector no
longer persists unset defaults and the same SHOW TBLPROPERTIES fails
with "does not have property" - which still means the modern default.
Accept the absent-property reply as the modern default while keeping
the assertion strong: a persisted "true" or any other failure still
fails, and the explicitly-set legacy table round trip is unchanged.
Verified: both suites pass locally against the 2.1 stack; the remaining
write-suite failures are the variant suites, which need a Spark 4.0
ThriftServer for VARIANT DDL and are out of scope for a local 3.5 run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
924060929
left a comment
There was a problem hiding this comment.
FE-only review of 2dcb9c0. The inline comments cover candidate filtering, vector cast semantics, relation read options, and projection eligibility. These are based on source-path analysis; no build or tests were run for this review.
|
/review |
|
Codex automated review failed and did not complete. Error: Review recovery stopped: No final review submission was declared Please inspect the workflow logs and rerun the review after the underlying issue is resolved. |
The community paimon 2.1.0 keeps the 1.4 semantics for aggregate-merge overflow: the merge wraps silently (a TINYINT sum of 127 and 1 reads back as -128 on both readers). The fail-loud "byte overflow" behavior the adaptation targeted only existed in the internal 2.1-community-release-2.1-BETA-SNAPSHOT build, so this reverts that suite adaptation (1bac394) and puts the wrapped-value comparisons back. Verified against the 2.1.0 stack: the ANN e2e suite passes and the micro-reproduced TINYINT sum merge reads back -128. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replace the internal 2.1-community-release-2.1-BETA-SNAPSHOT pin with the community 2.1.0 release. Verified against the 2.1.0 stack (FE+BE+Spark connector all on 2.1.0): - The community release restores reading manifests whose _FILE field is the legacy nullable union type, so the pre-provisioned paimon1 CI baseline (written by a writer older than 1.4) is readable again; the BETA build rejected it with "expected RECORD but found UNION". - The aggregate-merge overflow behavior matches 1.4 again (silent wrap, a TINYINT sum of 127 and 1 reads back as -128); the fail-loud "byte overflow" behavior only existed in the internal BETA, and the suite adaptation for it is reverted in the previous commit. - The S3FileIO still reads its credentials only from CatalogContext.options(), so the fs.s3a.* mirroring stays required. - End-to-end ANN regression (spark-built ivf-rq fixture) passes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…or paimon 2.1.0 The community paimon 2.1.0 changes the persisted timestamp-to-date conversion for pre-epoch values to take the preceding calendar date (floor, matching Arrow): a TIMESTAMP(3) of -1ms evolved to DATE reads back as day -1. Paimon 1.4.2 and the internal 2.1-BETA build truncated toward zero and returned day 0, which the original expectation pinned. The floor result is the calendar-correct value (1969-12-31), so the expectation moves to -1. Verified: run-fe-ut.sh --run PaimonRustReaderCompatibilityTest passes 22/22 against the 2.1.0 stack; this was the only failure among the 485 paimon-related FE UTs on 2.1.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…review) Addresses the P1/P2 review findings on PushDownVectorTopNIntoPaimonScan: P1 - Keep residual filtering before the bounded vector top-N. The BE paimon-rust predicate converter skips conjuncts it cannot represent (and the synthetic distance column is outside its field registry), and the engine re-applies those residuals on the returned block -- after the top-K rows were already selected. With candidate distances 0, 2 and 3, a `WHERE dis > 1 ORDER BY dis LIMIT 1` fetched only the distance-0 row and returned nothing. The rewrite now fires with a Filter only when every conjunct provably rides into the vector search: a comparison / IN / null-check on a table column whose Doris type is in the converter's supported set (mirrors PaimonRustPredicateConverter._is_supported_slot_type). Anything else declines the rewrite and the query computes the distance in Doris, which is always correct. Verified end to end: the differential leg caught the empty result on the unfixed build and passes on the fixed one. P1 - Do not strip value-changing casts from the indexed vector. A legal cast chain can change vector values (an ARRAY<FLOAT> -> ARRAY<INT> -> ARRAY<FLOAT> round trip loses precision), so searching the original vectors while the query means the casted ones changes the ranking. The rewrite now accepts a direct indexed column slot only. P1 - Preserve the relation's file-creation-time restriction on the vector path. The ordinary path drops manifest entries older than the bound, but the PK-vector search plans its own splits and never consults the bound, so old files' nearest neighbors could leak into the result. A scan carrying scan.file-creation-time-millis now declines the rewrite (snapshot / tag / version selectors are resolved into the table before the vector search and stay eligible). P2 - Check the projection capabilities before committing the rewrite. A PK-vector split has no JNI fallback, so a query whose projected columns would set the projectedVariant bound (nested Variant) or an ORC table with TIMESTAMP_LTZ in its schema -- both scan-node bounds with no vector-path fallback -- would turn a supported query into an error. Both are now pre-flighted and decline instead, keeping the query on an ordinary supported path. Tests: PushDownVectorTopNIntoPaimonScanTest grows from 20 to 27 cases (distance-predicate / unsupported-type / expression filters decline, a simple column filter still rides, file-creation-time declines while a snapshot selector does not, lossy cast declines, Variant projection declines; the two distance-predicate positives flip to the new sound semantics). The ANN regression suite adds the requested differential legs: a distance lower bound, an unconvertible ARRAY<FLOAT> predicate and a file-creation-time bound, each compared against the rust-disabled brute force. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replace the 3.1.4-SNAPSHOT dependency with its release build. The 4.1.0 artifact is byte-identical to the snapshot it replaces (same class list and file sizes, same shaded paimon 2.1.0 / hive 3.1.3, identical pom dependency declarations), so this only pins the FE build to a stable release version. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
run buildall |
|
/review |
There was a problem hiding this comment.
Static review of PR #68988 at 537b731f92999ca4821ba8645b946e40a23d2888: changes requested. Ten new verified findings cover nested projection correctness, partitioned ANN planning, shared Thrift IDs, BE test compilation, score ordering, NULL query coordinates, BE capability negotiation, first-row filter eligibility, ordinary physical-column preservation, and lossless binary test comparison.
Two existing P1 threads remain applicable and are carried without duplicate inline comments:
- 4254619627 — filtering before bounded TopK: the new whitelist accepts
tag IN (1,2,NULL)because every option is a Literal, but the BE converter rejects the entire IN predicate on its NULL literal. With nearest tag=0 and the next tag=2 in one bucket, LIMIT 1 retrieves tag=0 and the Doris residual removes it, returning no row. Typed NULL survives coercion, constant folding, IN simplification, and translation. The original distance-predicate example now declines rewriting, but the same truncation defect remains. - 4254619638 — resolved file-creation restriction:
t@options('scan.creation-time-millis'='<future>')is not excluded by the new rawscan.file-creation-time-millischeck. Its resolver falls back to the same pinned internal file-creation bound when no later snapshot exists. Ordinary planning excludes all old files; vector planning ignores that bound and can return their neighbors. The explicit file-creation option is guarded, but this equivalent resolved read state remains affected.
Existing 4254619634 (value-changing vector casts) is addressed by requiring a direct indexed slot. Existing 4254619644 (projected VARIANT / ORC LTZ) has explicit preflight guards; the new ordinary-STRUCT finding is a separate layout failure. No additional user-provided focus was supplied.
Applicable critical checkpoints:
- Goal and proof: the nominal SDK 2.1 / L2 and inner-product ANN flow is implemented, but the verified failures prevent ordinary-scan parity. Added unit and differential regression tests were inspected; none were executed.
- Scope and abstraction: reviewed every one of the 53 authoritative changed paths, including dependency/property changes and test utilities. AnnTopNInfo groups transport state and logical/physical copies retain it; the vector branch incorrectly bypasses some compatibility checks.
- Concurrency and locks: no new Doris threads or lock order introduced. Paimon range construction follows the sequential nonbatch path; a suspected logging-HashSet race was dismissed from actual callers. No heavy new operation was found inside a new lock.
- Lifecycle and initialization: traced relation/schema binding, pruning, rewrite, translator, processed table, bucket payload, Rust eager execution, Arrow materialization, residual evaluation, and coordinator TopN. Pinned C API setters copy inputs and read/plan ownership allows local RAII destruction. No new cross-translation-unit static initialization dependency or ownership cycle was established.
- Configuration: existing Rust-reader and FileScannerV2 switches are read per query. No new effective session/BE configuration is introduced; the SessionVariable change is whitespace.
- Compatibility: compared touched Thrift definitions with live branch-3.1, branch-4.0, branch-4.1, branch-4.2, and master. Two new field IDs collide with master. The generic existing BE capability does not distinguish the new vector protocol. Pinned Rust API availability and Java VECTOR access/type preservation were checked from source.
- Parallel paths and conditions: compared native, JNI, ordinary Rust, vector, system/delegate, raw-convertible, partition, nested, incremental and read-state paths. Incremental and direct lossy-cast cases decline. Remaining predicate/read-state restrictions are documented above and in the new inlines.
- Functional tests: new ANN regression uses a small unpartitioned DV-enabled fixture. It does not cover partitioned vector ranges, pruned STRUCT materialization, NULL query coordinates, non-DV first-row filters, mixed-version BEs, or cross-bucket score overflow.
- Negative/unit coverage: inspected score, nullable-column, payload, rewrite and routing tests. The newly incorrect include prevents the BE test target from compiling. No build or test was attempted under this static-only contract.
- Expected results: no golden
.outfiles changed. The pre-epoch timestamp-to-date expectation follows the SDK's floor behavior. The new UTF-8 comparator can conceal distinct binary results and needs a lossless comparison. - Observability: EXPLAIN exposes vector column, metric, vector and limit; FE logs distinct fallback reasons. Existing reader/open/read/Arrow timers cover the new lifetime; search/refine/materialization costs accumulate during reader opening.
- Transactions and persistence: production changes are query-side; no new Doris EditLog, commit, replay or master-failover state is introduced. External snapshot/file visibility and deletion-vector constraints were checked; the surviving read-state thread and first-row filter finding identify concrete gaps.
- Data writes and crashes: no production data-write atomicity change. JNI/native fallback and C error/cleanup paths were inspected statically. This review does not claim fault-injection or crash validation of the upgraded SDK.
- FE/BE propagation: vector/column/limit/metric travel through logical and physical copies, translator and little-endian FLOAT32 request; bucket payloads travel per split. Partition metadata, nested shape and protocol negotiation have the reported gaps.
- Performance and memory: projection/filter setup is outside per-row decode. Compacted raw split dispatch and C handle lifetime were inspected. Vector output is bounded by requested TopK, not adaptive scanner batch size; large TopK/wide payload memory, cancellation and full Hive-shade runtime interoperability remain unmeasured.
- Core invariants and other issues: query correctness/error preservation fail in the identified cases. No new internal rowset-version, MoW delete-bitmap or uncommitted-transaction path is changed. Arrow distance conversion checks concrete nullable columns and restores COW ownership; no additional memory-lifetime defect was established.
Coverage limitation: this is static source review, including pinned external source and live protocol references. No compilation, tests, runtime/external-writer interoperability, or performance measurements were performed. All changed paths have been read; empirical compatibility and memory behavior are not claimed.
Review converged after 3 round(s).
Existing P0/P1 findings confirmed for this head: #68988 (comment), #68988 (comment)
FE UT Coverage ReportIncrement line coverage `` 🎉 |
FE Regression Coverage ReportIncrement line coverage |
…NN path Static review of this PR surfaced twelve soundness and compatibility gaps on the PK-vector (ANN) search path; every one was verified against the pinned sources before the fix: - Wire protocol: TPaimonFileDesc.vector_payload moves from field 20 to 21 and TFileScanRangeParams.external_search_request from 41 to 42 -- both IDs are already reserved on master (original_file_path, fluss_properties), and reusing them would make a mixed-version cluster drop the payload instead of decoding it. BE/FE must upgrade together. - Vector capability negotiation: a new heartbeat bit supports_paimon_rust_vector_reader (TBackendInfo field 12, free on all branches) gates the one-way rewrite separately from the batch rust bit, so a rolling upgrade keeps ordinary rust reads working and only declines the vector rewrite against older BEs. - Partitioned vector splits now carry the derived partition values (PaimonSplit.setPaimonPartitionValues), which setPaimonParams demands for any table with partition keys. - The rewrite pre-flight now also declines: a query vector containing a NULL element (getDouble() would silently search a zero coordinate while the un-rewritten function raises); scan.creation-time-millis (resolves into the same internal file-creation bound the vector search never consults); a data predicate on a table without deletion vectors or with merge-on-read (paimon-rust rejects it AFTER the one-way rewrite); a table column named __paimon_search_score_to_dis (collides with the synthetic distance slot); and an expression over a complex column (nested pruning reduces the scan tuple while the vector reader projects only root names -- the struct SerDe then binds children by ordinal). The shape whitelist additionally rejects NullLiteral operands: the BE converter rejects a null literal and with it the whole predicate, leaving it in the post-fetch residual. The scan node keeps the nested-projection bound as a loud backstop for vector splits and logs the vector-capability reason. - BE: _convert_score_to_distance maps zero and out-of-range scores to +inf instead of negative sentinels -- a zero score is reachable from a genuine huge distance (the search-mode=full exact fallback computes squared L2 in f32), and -2 sorted ahead of every finite distance, inverting the coordinator's TopN. _build_read_columns skips the synthetic distance name only in vector mode, so an ordinary scan of a real column with that name keeps reading it. The BE test include path is fixed (the file never compiled). - Regression framework: the 0x-token decode pass only substitutes UTF-8-round-trippable bytes and keeps the lossless hex token otherwise -- replacement-character decoding made 0x80 and 0x81 compare equal. Verified: FE UT 36/36 (rewrite) + 96/0 (scan node) + 22/22 (rust compat) + 4/4 (capability); BE UT 30/30; the full ANN regression suite including the four new differential legs (no-deletion-vector first-row table, partitioned table, NULL query vector, IN with a NULL option). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
/review |
There was a problem hiding this comment.
Static review of all 59 authoritative changed paths at 1f40520. Request changes for six new findings: branch index/metric identity, historical scalar value loss, eager materialization memory, generic external column mapping, stored-NULL validation, and nonmovable projection errors.
Existing threads were revalidated and not reposted. Existing P1 4311684734 still applies when an outer field projection leaves an already-pruned bare slot inside the ANN Project: preflight accepts it and the final scan guard throws. Existing P1 4254619638 still applies to its explicitly noted pinned-empty generation bypass: a first commit after binding can be read by vector planning, while ordinary planning returns the fenced empty state. Existing P1 4254619627 still applies with a supported raised expr_children_limit and an IN list exceeding 65535 children: FE accepts it, the Rust predicate converter drops it, and residual filtering occurs after bounded retrieval. The original direct-field, file-time-bound, and ordinary unsupported-filter examples have guards now; these remaining instances preserve the existing blockers. Other earlier P1s (lossy casts, partition metadata, field-ID collisions, missing include and zero-score ordering) are repaired in the inspected head. Known P2 name-collision and type-unaware result-normalization residuals remain covered by their existing threads.
Critical checkpoint conclusions:
- Goal and proof: the PK-vector ANN/SDK2.1 flow is implemented, but the findings prevent correctness approval. Positive and negative UT/regression sources were inspected; the new boundary cases lack coverage.
- Scope: planner, request, split, reader and SDK changes follow the feature flow; shared eligibility removes duplicated gates. The generic mapping exception and skipped schema guarantees need correction.
- Concurrency: query state is per scan; no new lock/order or metadata mutation path found. Heartbeat capability uses existing volatile/replay handling. The empty-snapshot race is identified above.
- Lifecycle/static initialization: Rust builders/plans clone inputs; local RAII teardown and Arrow release ownership are sound. AnnTopNInfo clones its array. No new cross-TU static dependency or ownership cycle identified; eager buffers have the reported resource issue.
- Configuration: no new user setting; existing Rust/V2 switches are read during rewrite and scan planning.
- Compatibility: separate vector capability prevents dispatch to older batch-only BEs. SDK2.1/JNI VECTOR and S3 credential-option changes were traced; historical scalar ANN casts remain unsafe.
- Shared definitions: touched Thrift definitions were compared with master and branches 4.2/4.1/4.0/3.1. New field IDs 12/21/42/6 are free; no remaining collision identified. New payload is behind an optional envelope. No proto changes.
- Parallel paths: native/JNI/cpp/batch-Rust routes and raw compaction gates were checked. One-way ANN must preserve their schema, error, identity and memory guarantees.
- Special conditions: cast, query-NULL, projected VARIANT, creation-time and vector-capability guards are present. Remaining nested/empty/filter edges are recorded against existing threads.
- Tests: all changed tests/fixtures reviewed statically. Branch metric divergence, stored NULLs, nonmovable assertions, historical scalar ANN evolution and large-K memory require regression coverage.
- Expected results: pre-epoch DATE floor, unset property and error-message adaptations match the inspected SDK behavior. Existing ResultUtils type-awareness concern remains a duplicate fence; no new golden output file was added.
- Observability: ANN explain details, Rust lifecycle timers and deduplicated fallback reasons are present. No separate new critical logging defect established.
- Persistence/failover: no new external data writes or EditLog record type. Heartbeat persistence follows the existing pattern; read snapshot consistency remains blocked as above.
- Writes/crashes: no new transaction/write atomicity path. The eager memory issue can exhaust a BE during a read.
- FE-BE flow: vector/column/metric/K and bucket payload survive logical/physical copies and translation; partition values are populated. The branch finding concerns semantic identity despite complete transport.
- Performance/memory: FFI ownership and ordinary bounded batches were checked; eager all-K retention/copy lacks the ordinary bounds/accounting.
- Other invariants: NULL and assertion errors must remain failures. No additional deadlock, OLAP delete-bitmap or visible-version modification identified.
No additional user focus was supplied. Validation was static only: no builds, tests, source modifications or submodule initialization. Runtime/load and live mixed-version behavior were not exercised.
Review converged after 2 round(s).
Existing P0/P1 findings confirmed for this head: #68988 (comment), #68988 (comment), #68988 (comment)
| PaimonExternalTable paimonTable = (PaimonExternalTable) scan.getTable(); | ||
| CoreOptions coreOptions; | ||
| try { | ||
| Table basePaimonTable = paimonTable.getPaimonTable(Optional.empty()); |
There was a problem hiding this comment.
[P1] Validate the index against the relation's bound table
getPaimonTable(Optional.empty()) fetches the cached main table, while PaimonSource and vector split planning use this relation's retained branch/snapshot. For example:
TopN(dis ASC LIMIT 1)
Project(id, dis := l2_distance_approximate(vec, [0.25,0,...]))
Scan(t@branch(b))
Main can have an L2 index while branch b has an independently configured inner-product index. With branch vectors [1,0,...] and [2,0,...] in one bucket, this preflight accepts main's L2 metric, but Rust reads the branch schema and selects the second vector with score 0.5. BE then applies the requested L2 inverse and publishes distance 1; ordinary L2 selects the first vector at 0.75. Full recall still gives this wrong result. Resolve all index/metric/eligibility checks from the scan's bound table before replacing the expression, and cover differing branch index options.
| canUseRust = canUseRust && rustEligibility.vectorEligible() | ||
| && PaimonRustReaderCapabilities.hasFullNestedProjection(desc); | ||
| } | ||
| if (!vectorSearchSplit && canUseRust) { |
There was a problem hiding this comment.
[P1] Retain historical scalar compatibility checks for vector reads
canRead also protects historical value conversions, which remain relevant when vector search materializes projected columns. On a PK-vector Parquet table, write ts TIMESTAMP(3) = '2300-01-01 00:00:00.123', then widen ts to TIMESTAMP(9) while retaining the old files/index. SELECT ts, l2_distance_approximate(vec,q) AS d FROM t ORDER BY d LIMIT 1 now bypasses that check. Rust's materialization uses DataFileReader -> reconcile_column -> Arrow's safe millisecond-to-nanosecond cast; the year-2300 multiplication overflows and returns NULL. The ordinary path explicitly rejects this evolution in PaimonRustReaderCapabilities and uses JNI, which preserves the timestamp (also documented by testPersistedTimestampEvolutionIntoNanosecondRange). Check current/historical schema compatibility before committing ANN, or provide equivalent materialization. This is a scalar full-projection case, separate from the existing pruned-STRUCT thread.
| } | ||
| vector_plan_ptr plan(plan_result.plan); | ||
|
|
||
| paimon_result_record_batch_reader rdr_res = paimon_vector_read_read(read.get(), plan.get()); |
There was a problem hiding this comment.
[P2] Bound the eager materialization before entering Rust
This call finishes materialization before returning the Arrow reader. In the pinned Rust SDK, materialize_positions retains every projected Top-K batch, then reorder_and_strip_position copies those rows into one output batch. Unlike the ordinary path, this path ignores the scanner's adaptive batch size and has no reservation/accounting bridge for those Rust buffers. A one-bucket query with LIMIT 1000000 and a projected 4-KiB payload can therefore retain roughly 4 GiB plus another 4 GiB reorder copy during prepare_split, even with a tiny probe batch and query memory budget. Raw malloc interposition forwards to jemalloc; it does not perform Doris Allocator tracking. Bound and account for this eager work before the call, or use bounded materialization; splitting the returned batch afterward cannot prevent the allocation spike.
| // Reader-produced Paimon PK-vector search distance: a synthetic output column | ||
| // filled by the BE reader by name, not present in the table's file schema, | ||
| // so it has no Parquet/ORC column position to map. | ||
| if (PaimonVectorSearch.SEARCH_DISTANCE_COLUMN.equalsIgnoreCase(colName)) { |
There was a problem hiding this comment.
[P2] Limit this mapping exception to an actual synthetic slot
This method serves every external catalog, so the name alone also excludes a real Hive column. For a Hive text/CSV table (id INT, __paimon_search_score_to_dis STRING), selecting both columns leaves two required file slots but emits only column_idxs=[0]. With FileScannerV2, DelimitedTextReader::_init_reader then rejects the index-count mismatch; Hive positional ORC/Parquet mapping likewise lacks the second ordinal. Preserve the mapping for physical same-named columns and identify the actual Paimon vector-produced slot. This is a separate generic FE/native-scan instance from the already-discussed ordinary Rust _build_read_columns exclusion.
| if (((ArrayLiteral) right).getValue().stream().anyMatch(NullLiteral.class::isInstance)) { | ||
| return null; | ||
| } | ||
| SlotReference vectorSlot = (SlotReference) left; |
There was a problem hiding this comment.
[P2] Preserve validation of NULL vectors stored in the table
The RHS check above does not cover a live NULL in the indexed column:
TopN(dis ASC LIMIT 1)
Project(id, dis := l2_distance_approximate(vec, valid_q))
Scan(rows: vec=NULL; vec=[1,0,...])
Paimon permits nullable VECTOR columns and NULL stored values. The ordinary FunctionArrayDistance::_validate_no_nulls throws First argument ... cannot be null before calculating distances. The Rust vector reader instead skips outer-NULL vectors, so replacing the function makes this query successfully return the finite row, even with full recall. Both supported metrics have this difference. Preserve the stored-vector validation or decline the rewrite when its error contract cannot be guaranteed, and add an enabled/disabled NULL-data regression. This is distinct from the earlier NULL coordinate in the query literal: here the query vector is valid.
| replaceMap.put(orderKeyAlias, distanceSlot); | ||
| replaceMap.put(orderKeyExpr, distanceSlot); | ||
|
|
||
| Plan plan = scan.withVectorTopN( |
There was a problem hiding this comment.
[P2] Keep nonmovable project expressions before truncation
The rewrite accepts a Project containing NoneMovableFunction, such as:
TopN(dis ASC LIMIT 1)
Project(c := assert_true(id > 0, 'bad id'),
dis := l2_distance_approximate(vec,q))
Scan(rows: id=-1/far; id=1/near)
On the ordinary path the Project evaluates before TopN and raises for the far row. withVectorTopN truncates the scan to the near row first, so the assertion never sees id=-1 and the query succeeds. The scalar assertion passes complexColumnsRideAsWholeColumns, and there is no nonmovable-expression guard. Decline this rewrite for project expressions whose evaluation must remain below TopN, and cover the assertion case. This concerns a SELECT expression, separate from the existing WHERE residual-filter thread.
Six new findings, all verified and fixed: - Branch identity: the pre-flight resolved the cached main table while the scan reads the relation's bound branch table, so a branch with a different index metric scored and reordered with the wrong metric. All four pre-flight sites now resolve the bound table (relation snapshot, or the options-applied table for t@options); time-travel relations benefit too. - Historical scalar evolution: the vector path skipped the batch reader's per-file schema gate, so a TIMESTAMP(3)->(9) widening would NULL out-of-range values through Arrow's safe cast. The rewrite now declines any rust-incompatible schema history, and the scan node re-checks per split (loud backstop mirroring the pre-flight exactly). - Generic mapping exception: FileQueryScanNode skipped the synthetic distance name for every external catalog, breaking real same-named Hive columns; the skip now applies only when the schema has no such column. - Nonmovable projection: a NoneMovableFunction (assert_true) in the ANN project must keep evaluating below the TopN; any nonmovable expression outside the order-key alias now declines the rewrite. - Pruned bare slot: a bare complex slot whose type was already reduced by nested pruning now declines (slot type must still equal the original column's), instead of tripping the desc-side guard loudly. - IN beyond the converter cap: the whitelist mirrors the BE converter's 16-bit child count, so a raised expr_children_limit no longer drops the predicate into the post-fetch residual. Carried P1 threads closed at their narrowed edges: the pinned-empty generation is fenced in getVectorSearchSplits exactly like the ordinary path (a first commit after binding is no longer readable by vector planning), and the pruned-bare-slot / IN-cap cases above. The stored-NULL differential is kept as a documented limitation: a nullability-based decline cannot ship until PaimonExternalTable.loadSchema stops hardcoding isAllowNull=true (today every paimon column, even PK columns, maps as nullable, so the gate would disable the rewrite for every table). Follow-ups recorded in the review thread. Tests: rewrite rule 36 -> 42 cases, PaimonScanNode +3 (fence twin, loud per-file backstop, schemaId-matching encode); the ANN e2e suite stays green with a new assert_true error-parity leg. BE untouched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
What problem does this PR solve?
Follow-up to #66227, which introduced the paimon-rust reader. This PR upgrades the Paimon SDK to the community 2.1.0 release and ports the primary-key vector (ANN) search path onto the community stack:
ORDER BY l2_distance_approximate|inner_product_approximate(col, vector) LIMIT nis rewritten into a pushdown onto the paimon scan and served by the paimon-rust vector reader on BE.Problem Summary:
1. Upgrade Paimon to the community 2.1.0
S3FileIOreads S3 credentials only fromCatalogContext.options()(verified at bytecode level:S3FileIO#configure -> loadHadoopConfigFromContextnever touches the HadoopConfiguration, unlike 1.4; unchanged in the community 2.1.0). Doris keeps a catalog's storage credentials in the Hadoop conf only, so every s3-warehouse paimon catalog initialized the SDK with no credentials and failed withUnable to load credentials.mirrorHadoopS3OptionsToCatalogOptionsnow mirrors thefs.s3a.*entries onto the catalog options for the FileSystem/HMS/JDBC metastores, keeping user extras (path style, connection limits) flowing as well._FILEfield is the legacy nullable-union type (written by paimon versions older than 1.4), so the pre-provisioned paimon1 CI regression baseline and old production tables stay readable; the internal 2.1 BETA build rejected them withexpected RECORD but found UNION.1bac394) followed by its revert (1871ef6), a net zero for reviewers.2. PK-vector (ANN) search
PushDownVectorTopNIntoPaimonScan(registered inRewriter) matches a vector distascan and pushes the query vector, the indexed vector column, the metric and the limit down to the scan.A pre-flight check refuses incremental scans (t@incr), so an incremental query can never silently resolve to a snapshot read.AnnTopNInfocarries the pushed-down info fromLogicalFileScantoPhysicalFileScanand ontoPaimonScanNode(PhysicalPlanTranslatorsets it only inside thePAIMON_EXTERNAL_TABLEbranch); thePlanNodes.thriftadditions are additive fields.paimon_rust_table_readergrows the vector-search path, reading through the rust PK-vector build-op on this path (the rust vector readers do not consume it) — noted in the reader.enable_paimon_rust_readerandenable_file_scanner_v2switches; with either off, the query brute-forces through the normal scan.3. Unify the rust eligibility gates
PaimonRustEligibilityevaluates the 12 table-level/storage/BE checks once and is shared by the batch and vector paths. The vector gate is now computed independently of the batch gate (it previously inheritedfallbackRead/scan.moderestrictions from the batch path) and both branches share a common gate chain with a batch-only split-suitability suffix.Tests
PushDownVectorTopNIntoPaimonScanTest(positive / incremental-refused / gate-negative coverage),PaimonScanNodeTestgate coverage, plannerPaimonPredicateConvertertests,PaimonUtilTest; 485 paimon-related FE UTs pass against the 2.1.0 stack.test_paimon_pk_vector_ann: the suite builds its own fixture through the Spark ThriftServer — an ivf-rq PK-vector index withnlist = nprobe = 4on 512 deterministic 16-dim vectors, i.e. exhaustiverecall, so ANN must equal brute force by construction. It compares against the brute-force answer comsabled, asserts the ANN leg really executed on the paimon-rust vector reader (profile check against asilent brute-force pass), and covers both
l2andinner_productmetrics, residual filters and non-index projections.scan.creation-time-millisleg silently resolved to the latest snapshot; Doris-side resolution verified correct by hand-reproducing the queries), andassertSparkDorisResultEqualsnow decodes the0x-hex tokens Doris renders for VARBINARY nested in arrays/maps/structs so identical nested binary values compare equal across engines.SHOW TBLPROPERTIES) — the underlying behaviors are unchanged.external_table_p0/paimonregression suites against the 2.1.0 stack: read-path 34/37 and write-path 26/32 green (the rest need a Spark 4.0 ThriftServer for VARIANT DDL or pre-provisioned fixtures that exist only in CI), plus the HDFS-based suites against the original pre-provisioned paimon1 OSS baseline — no Doris-side regressions.Release note
The Paimon catalog now runs on the community Paimon 2.1.0 release. Queries of the form
ORDER BY l2_distance_approximate|inner_product_approximate(<vector col>, <query vector>) LIMIT nover paimon tables with a PK-vector index arepushed down into the paimon-rust vector reader (requires
enable_paimon_rust_readerand `enable_file_sy paimon versions older than 1.4 (legacy union manifests) remain readable.Check List (For Author)
Check List (For Reviewer who merge this PR)