Skip to content

Commit 364136b

Browse files
committed
feat(format-table): support reading and writing format tables
A format table is a directory of data files of one format with no snapshots and no manifests, laid out like a standard Hive table. Only `parquet` and `orc` are supported. `Catalog::GetFormatTable()` loads one, and the new `FormatTableCatalog` interface lets a catalog serve that call itself. A caller holding a table path keeps the interface it uses for every other table: `TableScan::Create()`, `TableRead::Create()`, `FileStoreWrite::Create()` and `FileStoreCommit::Create()` recognise a format table from the schema under the path and dispatch to it, the way Java Paimon serves both kinds through one `ReadBuilder` and one `BatchWriteBuilder`. `FormatTableScan`, `FormatTableRead`, `FormatTableWrite` and `FormatTableCommit` stay public for a caller that already holds a `FormatTable`. One `FormatTableLoader` answers the "is this a format table" question for all four entry points and hands back the schema it read, so the schema file is read once. Anything a context carries that a format table cannot honour is refused by name rather than quietly dropped. The scan discovers partitions by descending the directory layout - `key=value` by default, the bare value under `format-table.partition-path-only-value` - and packs a partition's files into splits of about `source.split.target-size`, each holding whole files. The read projects the table's columns, pushes a predicate into the file readers and applies it exactly on request, and rebuilds the partition columns from the directory names, since the data files themselves do not carry them. Partition completion, the predicate split and the read-schema order all come from `FieldMappingReader`, and a data file is opened through `DataFileReaderFactory`, which the managed table path uses too, so prefetch and the caches a `ReadContext` carries apply here as well. The write is two-phase: it stages each file under `_temporary/.tmp.<uuid>` beside where it will be published, the layout Java's `RenamingTwoPhaseOutputStream` stages under, and the commit renames them into place. An overwriting commit first clears the partitions it writes to, or the static partition it was given. Options are read through `CoreOptions`, so a default lives in one place. A split and a commit message are both public structs a caller may have built itself, so each is checked for path containment, visibility and partition binding before anything is read, renamed or deleted. Neither has a serialized form: a format table's plan has no cross-runtime encoding, so `Split::Serialize()` and `CommitMessage::Serialize()` refuse them and a plan is read within the process that made it. - `Catalog::GetTable()` and `Table::Create()` refuse a format table, and every other table type this library does not implement, instead of opening it as a managed table and looking for snapshots it never had. - Every catalog validates a new table's schema through one entry point, so none accepts a table another could not open; `RestCatalog::CreateTable()` runs it now too. - `PartitionPathUtils::GeneratePartitionPath()` takes the value-only layout as a parameter and refuses a partition value that cannot name a directory. The managed table path goes through it too. - `LazyConcatBatchReader` opens one file of a split at a time, names the file in every failure and keeps answering with the failure it stopped at, as `BatchReader` requires. `docs/source/user_guide/format_table.rst` describes the layout, the options, and what is still missing against Java, including `dynamic-partition-overwrite` and partition filters beyond equality.
1 parent 3b93d6a commit 364136b

78 files changed

Lines changed: 9816 additions & 131 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.

docs/source/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ API Reference
2727
api/commit
2828
api/scan
2929
api/read
30+
api/format_table
3031
api/predicate
3132
api/file_format
3233
api/file_system

docs/source/api/catalog.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ Interface
2727
.. doxygenclass:: paimon::Catalog
2828
:members:
2929

30+
.. doxygenclass:: paimon::FormatTableCatalog
31+
:members:
32+
:undoc-members:
33+
3034
.. doxygenclass:: paimon::Identifier
3135
:members:
3236
:undoc-members:

docs/source/api/format_table.rst

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
.. Licensed to the Apache Software Foundation (ASF) under one
2+
.. or more contributor license agreements. See the NOTICE file
3+
.. distributed with this work for additional information
4+
.. regarding copyright ownership. The ASF licenses this file
5+
.. to you under the Apache License, Version 2.0 (the
6+
.. "License"); you may not use this file except in compliance
7+
.. with the License. You may obtain a copy of the License at
8+
9+
.. http://www.apache.org/licenses/LICENSE-2.0
10+
11+
.. Unless required by applicable law or agreed to in writing,
12+
.. software distributed under the License is distributed on an
13+
.. "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
.. KIND, either express or implied. See the License for the
15+
.. specific language governing permissions and limitations
16+
.. under the License.
17+
18+
============
19+
Format Table
20+
============
21+
22+
.. _cpp-api-format-table:
23+
24+
Table
25+
=====
26+
27+
.. doxygenclass:: paimon::FormatTable
28+
:members:
29+
:undoc-members:
30+
31+
Read
32+
====
33+
34+
.. doxygenclass:: paimon::FormatTableScan
35+
:members:
36+
:undoc-members:
37+
38+
.. doxygenstruct:: paimon::FormatDataSplit
39+
:members:
40+
:undoc-members:
41+
42+
.. doxygenclass:: paimon::FormatTableRead
43+
:members:
44+
:undoc-members:
45+
46+
Write
47+
=====
48+
49+
.. doxygenclass:: paimon::FormatTableWrite
50+
:members:
51+
:undoc-members:
52+
53+
.. doxygenstruct:: paimon::FormatCommitMessage
54+
:members:
55+
:undoc-members:
56+
57+
.. doxygenclass:: paimon::FormatTableCommit
58+
:members:
59+
:undoc-members:

docs/source/user_guide.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ User Guide
3333
user_guide/data_types
3434
user_guide/primary_key_table
3535
user_guide/append_only_table
36+
user_guide/format_table
3637
user_guide/system_tables
3738
user_guide/write
3839
user_guide/commit

docs/source/user_guide/format_table.rst

Lines changed: 260 additions & 0 deletions
Large diffs are not rendered by default.

include/paimon/api.h

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,30 @@
2020

2121
#pragma once
2222

23-
#include "paimon/commit_context.h" // IWYU pragma: export
24-
#include "paimon/defs.h" // IWYU pragma: export
25-
#include "paimon/factories/factory.h" // IWYU pragma: export
26-
#include "paimon/file_store_commit.h" // IWYU pragma: export
27-
#include "paimon/file_store_write.h" // IWYU pragma: export
28-
#include "paimon/fs/file_system_factory.h" // IWYU pragma: export
29-
#include "paimon/memory/memory_pool.h" // IWYU pragma: export
30-
#include "paimon/predicate/predicate.h" // IWYU pragma: export
31-
#include "paimon/read_context.h" // IWYU pragma: export
32-
#include "paimon/reader/batch_reader.h" // IWYU pragma: export
33-
#include "paimon/record_batch.h" // IWYU pragma: export
34-
#include "paimon/result.h" // IWYU pragma: export
35-
#include "paimon/scan_context.h" // IWYU pragma: export
36-
#include "paimon/status.h" // IWYU pragma: export
37-
#include "paimon/table/source/table_read.h" // IWYU pragma: export
38-
#include "paimon/table/source/table_scan.h" // IWYU pragma: export
39-
#include "paimon/write_context.h" // IWYU pragma: export
23+
#include "paimon/commit_context.h" // IWYU pragma: export
24+
#include "paimon/defs.h" // IWYU pragma: export
25+
#include "paimon/factories/factory.h" // IWYU pragma: export
26+
#include "paimon/file_store_commit.h" // IWYU pragma: export
27+
#include "paimon/file_store_write.h" // IWYU pragma: export
28+
#include "paimon/fs/file_system_factory.h" // IWYU pragma: export
29+
#include "paimon/memory/memory_pool.h" // IWYU pragma: export
30+
#include "paimon/predicate/predicate.h" // IWYU pragma: export
31+
#include "paimon/read_context.h" // IWYU pragma: export
32+
#include "paimon/reader/batch_reader.h" // IWYU pragma: export
33+
#include "paimon/record_batch.h" // IWYU pragma: export
34+
#include "paimon/result.h" // IWYU pragma: export
35+
#include "paimon/scan_context.h" // IWYU pragma: export
36+
#include "paimon/status.h" // IWYU pragma: export
37+
#include "paimon/table/format/format_commit_message.h" // IWYU pragma: export
38+
#include "paimon/table/format/format_data_split.h" // IWYU pragma: export
39+
#include "paimon/table/format/format_table.h" // IWYU pragma: export
40+
#include "paimon/table/format/format_table_commit.h" // IWYU pragma: export
41+
#include "paimon/table/format/format_table_read.h" // IWYU pragma: export
42+
#include "paimon/table/format/format_table_scan.h" // IWYU pragma: export
43+
#include "paimon/table/format/format_table_write.h" // IWYU pragma: export
44+
#include "paimon/table/source/table_read.h" // IWYU pragma: export
45+
#include "paimon/table/source/table_scan.h" // IWYU pragma: export
46+
#include "paimon/write_context.h" // IWYU pragma: export
4047

4148
// IWYU pragma: begin_exports
4249
#include "paimon/realtime/realtime_context.h"

include/paimon/catalog/catalog.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ namespace paimon {
4040
using Instant = std::variant<std::string, int64_t>;
4141

4242
class Database;
43+
class FormatTable;
4344
class Table;
4445
class View;
4546
class Schema;
@@ -208,6 +209,23 @@ class PAIMON_EXPORT Catalog {
208209
/// snapshot id ascending, or an error status.
209210
virtual Result<std::vector<SnapshotInfo>> ListSnapshots(
210211
const Identifier& identifier, const std::string& branch = "") const = 0;
212+
213+
/// Gets a format table: a directory of data files laid out like a standard Hive table.
214+
///
215+
/// A format table is read and written through `FormatTableScan`, `FormatTableRead`,
216+
/// `FormatTableWrite` and `FormatTableCommit` rather than through the managed table path, so
217+
/// it is loaded through its own method instead of `GetTable()`.
218+
///
219+
/// Not virtual, since `Catalog` is derived from outside this library: a catalog that can load
220+
/// a format table itself derives from `FormatTableCatalog` as well, and this finds it with a
221+
/// `dynamic_cast`. Otherwise it reads the location and the schema through the methods above,
222+
/// which costs two requests that can disagree, and treats everything below the location as
223+
/// data.
224+
///
225+
/// @param identifier Identifier of the table to get.
226+
/// @return A result containing the format table, or an error status if the table does not
227+
/// exist or its `type` option is not `format-table`.
228+
Result<std::shared_ptr<FormatTable>> GetFormatTable(const Identifier& identifier) const;
211229
};
212230

213231
} // namespace paimon
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <memory>
22+
23+
#include "paimon/result.h"
24+
#include "paimon/visibility.h"
25+
26+
namespace paimon {
27+
28+
class FormatTable;
29+
class Identifier;
30+
31+
/// What a catalog implements when it can load a format table itself.
32+
///
33+
/// It is a separate base rather than a virtual on `Catalog`, which is exported and derived from
34+
/// outside this library: a virtual added there would have no slot in an already-compiled subclass.
35+
/// A catalog inherits this alongside `Catalog`, and `Catalog::GetFormatTable()` finds it with a
36+
/// `dynamic_cast`.
37+
///
38+
/// A catalog that does not implement it still serves format tables: `Catalog::GetFormatTable()`
39+
/// falls back to reading the location and the schema through the methods every catalog has.
40+
class PAIMON_EXPORT FormatTableCatalog {
41+
public:
42+
virtual ~FormatTableCatalog() = default;
43+
44+
/// Loads `identifier` as a format table.
45+
///
46+
/// The catalog decides both halves the fallback has to guess at: whether the location and the
47+
/// schema can be read in one round trip, and whether it put this table's metadata under the
48+
/// table path.
49+
///
50+
/// @param identifier Identifier of the table to load.
51+
/// @return A result containing the format table, or an error status if the table does not
52+
/// exist or its `type` option is not `format-table`.
53+
virtual Result<std::shared_ptr<FormatTable>> LoadFormatTable(
54+
const Identifier& identifier) const = 0;
55+
};
56+
57+
} // namespace paimon

include/paimon/defs.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,33 @@ struct PAIMON_EXPORT Options {
118118
/// "page-size" - Memory page size, default value 64 kb.
119119
static const char PAGE_SIZE[];
120120

121+
/// "type" - Type of the table. Default value is "table" (a managed paimon table);
122+
/// "format-table" declares a directory of plain data files laid out like a Hive table.
123+
static const char TYPE[];
124+
121125
/// "file.format" - Specify the message format of data files.
122126
/// Default value is parquet.
123127
static const char FILE_FORMAT[];
124128

129+
/// "format-table.file.compression" - File compression of a format table. It is consulted
130+
/// after "file.compression" and before the bare "compression" key an engine's own writer
131+
/// reads; when none of the three is set, the format decides.
132+
static const char FORMAT_TABLE_FILE_COMPRESSION[];
133+
134+
/// "format-table.partition-path-only-value" - Whether a format table names a partition
135+
/// directory by its value alone ("2025/01/") instead of "key=value"
136+
/// ("year=2025/month=01/"). Default false.
137+
static const char FORMAT_TABLE_PARTITION_PATH_ONLY_VALUE[];
138+
139+
/// "metastore.partitioned-table" - Whether a table's partitions are registered with the
140+
/// catalog, which then decides their visibility. Default false; paimon-cpp reads partitions
141+
/// from the directory layout and rejects a table that sets it.
142+
static const char METASTORE_PARTITIONED_TABLE[];
143+
144+
/// "file.suffix.include.compression" - Whether a data file's name carries the compression it
145+
/// was written with, for a format that records it inside the file. Default false.
146+
static const char FILE_SUFFIX_INCLUDE_COMPRESSION[];
147+
125148
/// "file-system" - Specify the file system.
126149
/// Default value is local.
127150
static const char FILE_SYSTEM[];
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
#pragma once
20+
21+
#include <cstdint>
22+
#include <map>
23+
#include <string>
24+
25+
#include "paimon/commit_message.h"
26+
#include "paimon/visibility.h"
27+
28+
namespace paimon {
29+
30+
/// One file a `FormatTableWrite` has written but not yet published.
31+
///
32+
/// The file is complete on disk under `temp_file_path`, which a scan skips; committing renames it
33+
/// to `file_path`, which is what makes it part of the table.
34+
///
35+
/// It is a `CommitMessage` so that a format table can be written and committed through
36+
/// `FileStoreWrite` and `FileStoreCommit` like any other table, as Java Paimon's
37+
/// `TwoPhaseCommitMessage` is. It names a staged path rather than files to record in a manifest,
38+
/// so `CommitMessage::Serialize()` refuses it: there is no cross-runtime encoding for one, and a
39+
/// write and its commit belong to the same process.
40+
struct PAIMON_EXPORT FormatCommitMessage : public CommitMessage {
41+
FormatCommitMessage(const std::string& _temp_file_path, const std::string& _file_path,
42+
const std::map<std::string, std::string>& _partition, int64_t _record_count,
43+
int64_t _file_size)
44+
: temp_file_path(_temp_file_path),
45+
file_path(_file_path),
46+
partition(_partition),
47+
record_count(_record_count),
48+
file_size(_file_size) {}
49+
50+
~FormatCommitMessage() override = default;
51+
52+
std::string ToString() const;
53+
54+
/// Path the data was written to: a hidden file a scan skips.
55+
std::string temp_file_path;
56+
/// Path the file is renamed to when the write is committed.
57+
std::string file_path;
58+
/// Partition the file belongs to, empty when the table is not partitioned.
59+
std::map<std::string, std::string> partition;
60+
/// Rows written to the file.
61+
int64_t record_count;
62+
/// Size of the written file in bytes.
63+
int64_t file_size;
64+
};
65+
66+
} // namespace paimon

0 commit comments

Comments
 (0)