Skip to content

Commit 1377476

Browse files
authored
Merge branch 'main' into codex/fetch-boost-from-oss
2 parents 09d0d0e + cd0d3ce commit 1377476

132 files changed

Lines changed: 13482 additions & 236 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,17 @@ Paimon C++ is a high-performance C++ implementation of [Apache Paimon](https://p
3232
- **File systems**: file system abstraction with built-in local and Jindo file system support.
3333
- **File formats**: file format abstraction with built-in ORC, Parquet, and Avro support.
3434
- **Runtime utilities**: memory pool and thread pool abstractions with default implementations.
35-
- **AI-Oriented Features**: supports RowTracking and DataEvolution mode and provides Global Index capabilities including bitmap index, B-tree index, DiskANN-based vector search with Lumina, and Lucene-based full-text search.
35+
- **AI-Oriented Features**: supports RowTracking and DataEvolution mode and provides Global Index
36+
capabilities including B-tree index, DiskANN-based vector search with Lumina, and Lucene-based
37+
full-text search.
3638
- **Compatibility**: compatibility with Apache Paimon Java format and communication protocols,
3739
including commit messages, data splits, and manifests.
3840

41+
> **Bitmap global index compatibility:** Java Paimon now uses a dedicated bitmap global index
42+
> format instead of the previously shared wrapped bitmap file index format.
43+
> Paimon C++ therefore currently treats the `bitmap` global index type as unsupported. The legacy
44+
> implementation remains in the codebase pending migration to the Java-compatible format.
45+
3946
Note: Linux x86_64 and macOS arm64 builds are currently verified.
4047

4148
## Write And Commit Example

benchmark/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ if(PAIMON_BUILD_BENCHMARKS)
5757
${PAIMON_BENCHMARK_STATIC_LINK_LIBS}
5858
test_utils_static
5959
Threads::Threads
60-
${CMAKE_DL_LIBS}
6160
${PAIMON_BENCHMARK_PLATFORM_LINK_LIBS}
6261
${PAIMON_BENCHMARK_LINK_TOOLCHAIN}
6362
EXTRA_INCLUDES

cmake_modules/ThirdpartyToolchain.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,9 @@ macro(build_arrow)
16531653

16541654
# libarrow.a calls dlsym; keep ${CMAKE_DL_LIBS} in the interface so -ldl is placed
16551655
# after libarrow.a on linkers that resolve symbols strictly left-to-right.
1656+
# Every library that uses dl itself (arrow here; also lucene and jindosdk::nextarch)
1657+
# declares it on its own interface the same way; consumers inherit it transitively
1658+
# and must not list ${CMAKE_DL_LIBS} again themselves.
16561659
target_link_libraries(arrow
16571660
INTERFACE zstd
16581661
snappy

docs/source/user_guide/data_types.rst

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,3 +217,44 @@ and `Arrow DataTypes <https://arrow.apache.org/docs/format/Columnar.html#data-ty
217217

218218
The type can be declared using ``ROW<n0 t0 'd0', n1 t1 'd1', ...>`` where n
219219
is the unique name of a field, t is the logical type of a field, d is the description of a field.
220+
221+
* - ``VARIANT``
222+
- Struct
223+
- Data type of semi-structured data (e.g. JSON). A variant value contains one of:
224+
a primitive (e.g. integer, string), an array of variant values, or an object
225+
mapping string keys to variant values.
226+
227+
Variant values are encoded with two binaries following the parquet-format
228+
`Variant Binary Encoding <https://github.com/apache/parquet-format/blob/master/VariantEncoding.md>`_
229+
specification (compatible with the Java Paimon / Spark implementation). In
230+
C++ Paimon, a variant field is represented in an Arrow schema as
231+
``Struct{value: Binary NOT NULL, metadata: Binary NOT NULL}`` marked with
232+
Paimon-specific field metadata; use ``paimon::Variant::ArrowField`` to
233+
construct such a field, and ``paimon::Variant`` (``FromJson``/``ToJson``/
234+
``VariantGet``) to build and inspect values.
235+
236+
Only the parquet file format supports VARIANT columns. VARIANT cannot be
237+
used as a primary key, partition key or bucket key, and no predicate
238+
pushdown applies to it.
239+
240+
When writing, variant columns can optionally be *shredded* into typed
241+
parquet columns per the parquet-format
242+
`Variant Shredding <https://github.com/apache/parquet-format/blob/master/VariantShredding.md>`_
243+
specification by setting ``variant.shreddingSchema`` to a ROW type JSON
244+
whose fields map top-level variant column names to their shredding
245+
types. Alternatively, setting ``variant.inferShreddingSchema`` to
246+
``true`` infers a shredding schema per file from the first written rows
247+
(tuned by ``variant.shredding.maxSchemaWidth``, which bounds the total
248+
number of shredded fields across all variant columns of the schema,
249+
``variant.shredding.maxSchemaDepth``,
250+
``variant.shredding.minFieldCardinalityRatio`` and
251+
``variant.shredding.maxInferBufferRow``). Inference also covers variant
252+
columns nested inside ROW columns (variants inside arrays or maps stay
253+
unshredded, as in Java Paimon). Readers reassemble shredded columns
254+
transparently.
255+
256+
When reading, instead of the full variant, specific paths can be
257+
extracted by replacing the variant column in the read schema with a
258+
projection built by ``paimon::VariantAccessBuilder`` (e.g. ``$.a.b`` as
259+
BIGINT). For shredded files only the required typed sub-columns are
260+
read.

include/paimon/data/variant.h

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
/*
2+
* Copyright 2026-present Alibaba Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#pragma once
18+
19+
#include <cstdint>
20+
#include <memory>
21+
#include <optional>
22+
#include <string>
23+
#include <string_view>
24+
#include <unordered_map>
25+
26+
#include "paimon/memory/memory_pool.h"
27+
#include "paimon/predicate/literal.h"
28+
#include "paimon/result.h"
29+
#include "paimon/visibility.h"
30+
31+
struct ArrowArray;
32+
struct ArrowSchema;
33+
34+
namespace paimon {
35+
36+
/// Arguments controlling how a variant value is cast to a target type in `Variant::VariantGet`.
37+
struct PAIMON_EXPORT VariantCastArgs {
38+
/// Whether an invalid cast fails the call (true) or yields SQL NULL (false).
39+
bool fail_on_error = true;
40+
/// The time zone used when rendering TIMESTAMP values. Supported forms are `UTC`/`Z`/`GMT`,
41+
/// fixed offsets such as `+08:00`, and IANA region ids such as `Asia/Shanghai`.
42+
std::string zone_id = "UTC";
43+
};
44+
45+
/// A Variant represents a type that contains one of: 1) Primitive: A type and corresponding
46+
/// value (e.g. INT, STRING); 2) Array: An ordered list of Variant values; 3) Object: An
47+
/// unordered collection of string/Variant pairs (i.e. key/value pairs). An object may not
48+
/// contain duplicate keys.
49+
///
50+
/// A Variant is encoded with 2 binaries: the value and the metadata, following the parquet
51+
/// Variant Binary Encoding specification (compatible with the Java / Spark implementation). The
52+
/// encoding allows representation of semi-structured data (e.g. JSON) in a form that can be
53+
/// efficiently queried by path.
54+
///
55+
/// In an Arrow schema, a Variant field is represented as
56+
/// `struct<value: binary not null, metadata: binary not null>` marked with Paimon-specific field
57+
/// metadata; use `Variant::ArrowField` to construct such a field.
58+
class PAIMON_EXPORT Variant {
59+
public:
60+
~Variant();
61+
62+
/// Parses a JSON string as a Variant (duplicate object keys are rejected).
63+
///
64+
/// @param json The JSON document text.
65+
/// @param pool The memory pool used for the variant buffers.
66+
/// @return A result containing the created variant or an error.
67+
static Result<std::unique_ptr<Variant>> FromJson(const std::string& json,
68+
const std::shared_ptr<MemoryPool>& pool);
69+
70+
/// Creates a Variant from already-encoded value and metadata binaries (copied into `pool`).
71+
///
72+
/// @param value The variant value binary.
73+
/// @param value_length The length of the value binary.
74+
/// @param metadata The variant metadata binary.
75+
/// @param metadata_length The length of the metadata binary.
76+
/// @param pool The memory pool used for the variant buffers.
77+
/// @return A result containing the created variant or an error.
78+
static Result<std::unique_ptr<Variant>> Create(const char* value, uint64_t value_length,
79+
const char* metadata, uint64_t metadata_length,
80+
const std::shared_ptr<MemoryPool>& pool);
81+
82+
/// The variant value binary. The view remains valid as long as this variant exists.
83+
std::string_view Value() const;
84+
85+
/// The variant metadata binary. The view remains valid as long as this variant exists.
86+
std::string_view Metadata() const;
87+
88+
/// The size of the variant in bytes (value size + metadata size).
89+
int64_t SizeInBytes() const;
90+
91+
/// Stringifies the variant in JSON format.
92+
///
93+
/// @param zone_id The time zone used when rendering TIMESTAMP values.
94+
/// @return A result containing the JSON text or an error.
95+
Result<std::string> ToJson(const std::string& zone_id = "UTC") const;
96+
97+
/// Extracts a sub-variant value according to a path which starts with a `$`, e.g. `$.key`,
98+
/// `$['key']`, `$["key"]`, `$.array[0]`, and casts the value to the target type.
99+
///
100+
/// @param path The extraction path.
101+
/// @param target_type The target Arrow type (C data interface, consumed by the call); only
102+
/// scalar types are supported. Use `VariantGetArrow` for nested targets.
103+
/// @param cast_args Cast behavior arguments.
104+
/// @return A result containing the extracted literal, or nullopt for SQL NULL (unmatched
105+
/// path, variant null, or an invalid cast with `fail_on_error == false`).
106+
Result<std::optional<Literal>> VariantGet(const std::string& path,
107+
struct ArrowSchema* target_type,
108+
const VariantCastArgs& cast_args) const;
109+
110+
/// Extracts a sub-variant value according to a path which starts with a `$` and casts the
111+
/// value to the (possibly nested) target field type.
112+
///
113+
/// In addition to scalar types, the target may be a STRUCT (cast from a variant object,
114+
/// children matched by field name; unmatched children are null), a MAP with string keys
115+
/// (cast from a variant object), a LIST (cast from a variant array), or a variant-marked
116+
/// field created by `Variant::ArrowField` (the sub-variant is deeply re-encoded).
117+
///
118+
/// @param path The extraction path.
119+
/// @param target_field The target Arrow field (C data interface, consumed by the call).
120+
/// @param cast_args Cast behavior arguments.
121+
/// @return A result containing a length-1 Arrow array of the target type whose single slot
122+
/// holds the result; a null slot represents SQL NULL (unmatched path, variant null,
123+
/// or an invalid cast with `fail_on_error == false`).
124+
Result<std::unique_ptr<struct ArrowArray>> VariantGetArrow(
125+
const std::string& path, struct ArrowSchema* target_field,
126+
const VariantCastArgs& cast_args) const;
127+
128+
/// Extracts a sub-variant value according to a path which starts with a `$` and renders it
129+
/// as JSON text.
130+
///
131+
/// @param path The extraction path.
132+
/// @param zone_id The time zone used when rendering TIMESTAMP values.
133+
/// @return A result containing the JSON text, or nullopt if the path does not match.
134+
Result<std::optional<std::string>> VariantGetJson(const std::string& path,
135+
const std::string& zone_id = "UTC") const;
136+
137+
/// Creates an Arrow field definition for the Variant type.
138+
///
139+
/// This function constructs an Arrow Field (internally
140+
/// `struct<value: binary not null, metadata: binary not null>`) and exports it to the C data
141+
/// interface structure `::ArrowSchema`. It automatically injects Paimon-specific metadata to
142+
/// identify the field as a VARIANT.
143+
///
144+
/// @param field_name The name of the Arrow field.
145+
/// @param nullable Whether the field is nullable.
146+
/// @param metadata A map of key-value metadata to be attached to the field.
147+
/// @return A result containing a unique pointer to the generated `::ArrowSchema` or an error.
148+
static Result<std::unique_ptr<struct ArrowSchema>> ArrowField(
149+
const std::string& field_name, bool nullable = true,
150+
std::unordered_map<std::string, std::string> metadata = {});
151+
152+
private:
153+
class Impl;
154+
155+
explicit Variant(std::unique_ptr<Impl>&& impl);
156+
157+
std::unique_ptr<Impl> impl_;
158+
};
159+
160+
/// Builds a variant-access projection field: a struct field that replaces a VARIANT column in
161+
/// the read schema so that, instead of the full variant, only the described paths are extracted
162+
/// at read time (reading only the required shredded sub-columns from shredded files).
163+
///
164+
/// Example: read `$.age` as INT64 and `$.city` as STRING from variant column `v`:
165+
///
166+
/// VariantAccessBuilder builder;
167+
/// builder.AddField(age_type, "$.age");
168+
/// builder.AddField(city_type, "$.city");
169+
/// auto field = builder.Build("v"); // use in ReadContextBuilder::SetReadSchema
170+
///
171+
/// The resulting struct has one child per added field, named by its position ("0", "1", ...).
172+
class PAIMON_EXPORT VariantAccessBuilder {
173+
public:
174+
VariantAccessBuilder();
175+
~VariantAccessBuilder();
176+
177+
/// Adds an extracted field.
178+
///
179+
/// @param target_type The Arrow type the extracted value is cast to (C data interface,
180+
/// consumed by the call). Nested targets follow `Variant::VariantGetArrow` semantics.
181+
/// @param path The extraction path, e.g. `$.a.b` or `$.array[0]`.
182+
/// @param fail_on_error Whether an invalid cast fails the read (true) or yields SQL NULL.
183+
/// @param zone_id The time zone used when rendering TIMESTAMP values.
184+
Status AddField(struct ArrowSchema* target_type, const std::string& path,
185+
bool fail_on_error = true, const std::string& zone_id = "UTC");
186+
187+
/// Builds the projection field named `field_name`, to be used in the read schema in place
188+
/// of the variant column with the same name.
189+
Result<std::unique_ptr<struct ArrowSchema>> Build(const std::string& field_name) const;
190+
191+
private:
192+
class Impl;
193+
194+
std::unique_ptr<Impl> impl_;
195+
};
196+
197+
} // namespace paimon

include/paimon/defs.h

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ enum class FieldType {
4646
MAP = 14,
4747
STRUCT = 15,
4848
BLOB = 16,
49+
VARIANT = 17,
4950
UNKNOWN = 128,
5051
};
5152

@@ -414,6 +415,28 @@ struct PAIMON_EXPORT Options {
414415
/// Only effective when map.storage-layout = shared-shredding.
415416
static const char MAP_SHARED_SHREDDING_COLUMN_PLACEMENT_POLICY[];
416417

418+
/// "variant.shreddingSchema" - The Variant shredding schema for writing: a ROW type JSON
419+
/// whose fields map variant column names to their shredding types. No default value.
420+
static const char VARIANT_SHREDDING_SCHEMA[];
421+
/// "parquet.variant.shreddingSchema" - Fallback key of "variant.shreddingSchema".
422+
static const char PARQUET_VARIANT_SHREDDING_SCHEMA[];
423+
/// "variant.inferShreddingSchema" - Whether to automatically infer the shredding schema when
424+
/// writing Variant columns. Default value is "false".
425+
static const char VARIANT_INFER_SHREDDING_SCHEMA[];
426+
/// "variant.shredding.maxSchemaWidth" - Maximum number of shredded fields allowed in an
427+
/// inferred schema. Default value is 300.
428+
static const char VARIANT_SHREDDING_MAX_SCHEMA_WIDTH[];
429+
/// "variant.shredding.maxSchemaDepth" - Maximum traversal depth in Variant values during
430+
/// schema inference. Default value is 50.
431+
static const char VARIANT_SHREDDING_MAX_SCHEMA_DEPTH[];
432+
/// "variant.shredding.minFieldCardinalityRatio" - Minimum fraction of rows that must contain
433+
/// a field for it to be shredded. Fields below this threshold stay in the un-shredded
434+
/// Variant binary. Default value is 0.1.
435+
static const char VARIANT_SHREDDING_MIN_FIELD_CARDINALITY_RATIO[];
436+
/// "variant.shredding.maxInferBufferRow" - Maximum number of rows to buffer for schema
437+
/// inference. Default value is 4096.
438+
static const char VARIANT_SHREDDING_MAX_INFER_BUFFER_ROW[];
439+
417440
/// "blob-as-descriptor" - Read blob field using blob descriptor rather than blob
418441
/// bytes. Default value is "false".
419442
static const char BLOB_AS_DESCRIPTOR[];
@@ -440,13 +463,14 @@ struct PAIMON_EXPORT Options {
440463
/// path and requires manual configuration by the user. No default value.
441464
static const char BLOB_VIEW_UPSTREAM_WAREHOUSE[];
442465
/// "blob-write-null-on-missing-file" - Whether to write NULL for a descriptor BLOB value when
443-
/// the referenced file does not exist at write time. When false, the write fails when the
444-
/// descriptor is read. Default value is "false".
466+
/// the referenced file does not exist at write time. When false, a missing file is treated
467+
/// like any other fetch failure, following "blob-write-null-on-fetch-failure". Default value
468+
/// is "false".
445469
static const char BLOB_WRITE_NULL_ON_MISSING_FILE[];
446470
/// "blob-write-null-on-fetch-failure" - Whether to write NULL for a descriptor BLOB value when
447471
/// the referenced data cannot be fetched at write time (e.g. invalid descriptor or invalid
448-
/// offset). A missing file is handled by "blob-write-null-on-missing-file". When false, the
449-
/// write fails when the descriptor is read. Default value is "false".
472+
/// offset). A missing file is handled by "blob-write-null-on-missing-file" when that option is
473+
/// enabled. When false, the write fails when the descriptor is read. Default value is "false".
450474
static const char BLOB_WRITE_NULL_ON_FETCH_FAILURE[];
451475
/// "global-index.enabled" - Whether to enable global index for scan. Default value is "true".
452476
static const char GLOBAL_INDEX_ENABLED[];

include/paimon/global_index/global_index_reader.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class PAIMON_EXPORT GlobalIndexReader : public FunctionVisitor<std::shared_ptr<G
4747
/// @return true if the reader is thread-safe; false otherwise.
4848
virtual bool IsThreadSafe() const = 0;
4949

50-
/// @return An identifier representing the index type. (e.g., "bitmap", "lumina").
50+
/// @return An identifier representing the index type. (e.g., "btree", "lumina").
5151
virtual std::string GetIndexType() const = 0;
5252
};
5353

include/paimon/global_index/global_index_write_task.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class PAIMON_EXPORT GlobalIndexWriteTask {
3636
///
3737
/// @param table_path Path to the table root directory where index files are stored.
3838
/// @param field_name Name of the indexed column (must be present in the table schema).
39-
/// @param index_type Type of global index to build (e.g., "bitmap", "lumina").
39+
/// @param index_type Type of global index to build (e.g., "btree", "lumina").
4040
/// @param index_split The indexed split containing the actual data (e.g., Parquet file) and
4141
// row id range [from, to] for data to build index.
4242
/// The range must be fully contained within the data covered

0 commit comments

Comments
 (0)