blob: adebaecbc815612780962561cd2127efca746730 [file]
.. Licensed to the Apache Software Foundation (ASF) under one
.. or more contributor license agreements. See the NOTICE file
.. distributed with this work for additional information
.. regarding copyright ownership. The ASF licenses this file
.. to you under the Apache License, Version 2.0 (the
.. "License"); you may not use this file except in compliance
.. with the License. You may obtain a copy of the License at
.. http://www.apache.org/licenses/LICENSE-2.0
.. Unless required by applicable law or agreed to in writing,
.. software distributed under the License is distributed on an
.. "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
.. KIND, either express or implied. See the License for the
.. specific language governing permissions and limitations
.. under the License.
Read
====
Paimon by functionality can be divided into two layers:
- Control Plane: Responsible for accessing and managing Meta (snapshot, manifest, etc.), including:
- Catalog / Database access
- Table retrieval
- Collection and resolution of data files
- Data Plane: Responsible for accessing actual data files, including:
- Readers for various file formats
- Coordinated reading of file collections
The control plane and data plane interact primarily via DataSplit (the query plan). Paimon C++ currently supports a standard
DataSplit protocol which includes the necessary meta information to access data files. With DataSplit, a high-performance
data access path can be integrated.
At compute time, the execution engine (reader) does not need to be aware of the concrete table type or its metadata details.
It only needs to follow the instructions within the DataSplit (query plan) to perform data reading operations.
With the layered abstraction of the control plane and data plane, and the use of DataSplit as a stable protocol interface,
the two layers can evolve their functionality and optimize code relatively independently. This design also enables
cross-language task scheduling and interaction (e.g., Java and C++), substantially reducing engineering maintenance costs
across the two language ecosystems.
Schema Evolution
-----------------------
Scope and Compatibility
~~~~~~~~~~~~~~~~~~~~~~~~
Paimon C++ supports all evolution kinds available in Java Paimon for non-nested types:
- Add column
- Drop column
- Reorder columns
- Rename column
- Change column type
.. note::
- Only non-nested type evolution is supported. Nested columns (struct, array, map) are not supported.
- Partition keys: Only column reordering is supported; other operations are not supported (consistent with Java Paimon).
- Primary key:
- Adding or dropping columns is not supported.
- Other operations are supported (consistent with Java Paimon).
Per-File Schema via Field IDs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In DataSplit, each file may have a completely different data schema. Paimon uses field IDs to uniquely identify fields.
Overflow Behavior Disclaimer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Overflow behavior is undefined for C++ and Java Paimon. Results in overflow scenarios may:
- Be incorrect values,
- Return an error status,
- Or be null.
Paimon C++ does not guarantee identical results to Java Paimon in overflow scenarios. Users should not rely on identical
return values between implementations.
One exception: casting ``float`` or ``double`` to an integer type is well defined and matches Java on every supported
architecture; see note 2️⃣ below the matrix.
Type Change Support Matrix
~~~~~~~~~~~~~~~~~~~~~~~~~~
The table below indicates support for changing a column type from ``source`` to ``target``. Refer to the numbered notes below the table
for caveats.
.. list-table::
:header-rows: 1
:widths: 12 10 10 10 10 10 10 8 12 10 8 18 10
* - src \\ target
- tinyint
- smallint
- int
- bigint
- float
- double
- bool
- string
- binary
- date
- timestamp (without tz)
- decimal
* - tinyint
-
-
-
-
-
-
-
-
-
-
-
-
* - smallint
- 1️⃣
-
-
-
-
-
-
-
-
-
-
-
* - int
- 1️⃣
- 1️⃣
-
-
-
-
-
-
-
-
- 1️⃣
-
* - bigint
- 1️⃣
- 1️⃣
- 1️⃣
-
-
-
-
-
-
-
- 6️⃣
-
* - float
- 2️⃣
- 2️⃣
- 2️⃣
- 2️⃣
-
-
-
- 3️⃣ 4️⃣
-
-
-
-
* - double
- 2️⃣
- 2️⃣
- 2️⃣
- 2️⃣
- 2️⃣
-
-
- 3️⃣ 4️⃣
-
-
-
-
* - bool
-
-
-
-
-
-
-
-
-
-
-
-
* - string
-
-
-
-
- 3️⃣
- 3️⃣
-
-
-
-
- 5️⃣
- 7️⃣
* - binary
-
-
-
-
-
-
-
-
-
-
-
-
* - date
-
-
-
-
-
-
-
-
-
-
- 5️⃣
-
* - timestamp (without tz)
-
-
- 1️⃣
-
-
-
-
-
-
-
-
-
* - decimal
- 1️⃣
- 1️⃣
- 1️⃣
- 1️⃣
-
-
-
-
-
-
-
-
.. admonition:: Overflow Behavior Notes
:class: note
1️⃣ Integer downcast overflow behavior matches Java in specific cases.
Example: smallint -> tinyint, 32767 becomes -1; int -> smallint, -2147483648 becomes 0.
2️⃣ Casting float/double to an integer type follows Java semantics on every supported architecture:
``NaN`` becomes 0, an out-of-range value saturates at the int32 bounds (int64 for ``bigint``),
and the result is narrowed to the target width by keeping the low bits, the way Java
narrowing does.
Example: float -> tinyint, C++ and Java both: ``MAX_FLOAT -> -1``, ``INFINITY -> -1``,
``NaN -> 0``, ``300.9 -> 44``.
double -> float overflow produces ``Infinity`` / ``-Infinity``, consistent with Java.
3️⃣ Keyword differences for special float/double values:
- Java: Infinity, -Infinity, NaN
- C++: inf, -inf, nan
4️⃣ Printing difference:
- C++ prints 1.0 as ``1``
- Java prints 1.0 as ``1.0``
5️⃣ Timestamp precision and range differences:
- Java: 0000-01-01 00:00:00.000000000 to 9999-12-31 23:59:59.999999999
- C++: 1677-09-21 00:12:43.145224192 to 2262-04-11 23:47:16.854775807
- C++ only supports nanosecond precision; range is smaller.
6️⃣ bigint -> timestamp range differences:
- Java (ms): ``[MIN_INT64/1000, MAX_INT64/1000]`` seconds
- C++ (ns): ``[MIN_INT64/1e9, MAX_INT64/1e9]`` seconds
7️⃣ string -> decimal with precision > 38:
- C++ returns ``null`` if parsing would overflow 128-bit arithmetic.
- Java may rescale and return a value based on the rescaled precision.
- Example input: ``1111111111111111111111111111111111111.15``, Java returns: ``1111111111111111111111111111111111111.2``, C++ returns: ``null``
Implementation Guidance
~~~~~~~~~~~~~~~~~~~~~~~
- Use DataSplit as the sole interface between control and data planes. Treat it as the canonical query plan contract.
- Resolve field types and IDs per file; prefer inline data file metadata, fallback to table schema files when necessary.
- Expect per-file schema variability; design readers to align by field IDs rather than positional indices.
- Do not assume identical overflow semantics across C++ and Java; tests should validate acceptable ranges and nullability.
- For timestamp handling, consider precision/range constraints in C++ when interoperating with Java-produced data splits.
.. _data-evolution-deletion-vectors:
Deletion Vectors on Data-Evolution Tables
-----------------------------------------
A data-evolution table (``data-evolution.enabled = true``) may enable
``deletion-vectors.enabled``. Reading such a table is supported: a deleted row disappears
from the result, including from the columns merged out of the other files that cover it.
How the Deletion Vector Is Located
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A data-evolution split holds several files per row id range, one per group of columns. The
deletion vector of such a row range group is not per file: it is maintained against the
group's *anchor file*, the oldest normal file of the group, compared by
``(max_sequence_number, file_name)`` and skipping blob and vector-store files. Its positions
are therefore relative to the anchor file's row id range.
Reading applies that one vector to every file of the group, shifted by the file's offset
inside the anchor range, so all the readers being merged drop the same rows and stay
positionally aligned. The blob fallback path has no file reader to wrap for the placeholder
gaps it pads uncovered row ids with, so it removes the deleted row ids from those gap ranges
instead.
The rule that picks the anchor has to stay identical to the engine that writes the vectors:
a vector keyed by any other file of the group is never found, and its deleted rows silently
come back.
Limitations
~~~~~~~~~~~
- Only the default 32-bit deletion vectors can be read. ``deletion-vectors.bitmap64`` is not
supported yet, and a read fails when it actually encounters a 64-bit deletion vector.
- Paimon C++ does not write deletion vectors for data-evolution tables, so the deletes
themselves have to be issued by another engine.
- A commit that drops data files from such a table, an overwrite for instance, is refused.
Whether it conflicts with a concurrent commit rewriting those files' deletion vectors cannot
be decided yet, so it fails rather than committing against a stale state. Appending is
unaffected, and so is another engine replacing a deletion vector.
- Such a table is never compacted; see
:ref:`the compaction note <data-evolution-deletion-vectors-compaction>`.