@@ -33,18 +33,86 @@ A partitioned format table uses the standard Hive directory layout, and its part
3333discovered from that layout rather than from metadata. By default a partition directory is named
3434``key=value ``; setting ``format-table.partition-path-only-value `` names it by the value alone.
3535
36- Because a directory of plain files records no row identity, a format table only accepts inserts,
37- and reads return the table's own columns with no ``_VALUE_KIND `` field.
36+ Because a directory of plain files records no row identity, a format table only accepts inserts.
37+ Reads still carry the leading ``_VALUE_KIND `` field every ``BatchReader `` promises, so an engine
38+ that reads a batch by field index sees the same layout it does for a managed table; every row of a
39+ format table is an insert.
3840
3941Reading and writing
4042-------------------
4143A format table is not served through :cpp:func: `Catalog::GetTable `, which describes a managed
42- table; use :cpp:func: `Catalog::GetFormatTable ` instead. It is then read and written through
43- ``FormatTableScan ``, ``FormatTableRead ``, ``FormatTableWrite `` and ``FormatTableCommit `` rather
44- than through the managed table path.
44+ table; use :cpp:func: `Catalog::GetFormatTable ` instead.
4545
46- A write is two-phase, since a directory has no metadata to switch atomically: written files are
47- staged under hidden names that a scan skips, and only the commit renames them into place.
46+ Reading and writing take either of two routes. The generic entry points read the table's schema -
47+ from under the table path, or from the one the context carries - and dispatch to a format table
48+ when its ``type `` says so, so a caller holding a table path uses the same interface it uses for
49+ every other table - ``TableScan::Create ``, ``TableRead::Create ``, ``FileStoreWrite::Create ``
50+ and ``FileStoreCommit::Create ``, each from its usual context builder. That is what Java Paimon does
51+ through ``FormatTable.newReadBuilder() `` and ``newBatchWriteBuilder() ``. The format-specific classes
52+ ``FormatTableScan ``, ``FormatTableRead ``, ``FormatTableWrite `` and ``FormatTableCommit `` remain
53+ public and take a ``FormatTable `` directly, for a caller that already has one and wants the narrower
54+ interface; Java keeps its own equivalents public for the same reason.
55+
56+ Not all of the generic interfaces fit. ``FileStoreCommit `` is mostly about snapshots and manifests -
57+ expiring them, rolling back to one, filtering by a commit identifier recorded in one - and a format
58+ table keeps none of that state, so those calls are refused rather than quietly doing nothing.
59+ ``FileStoreWrite::Compact() `` is refused for the same reason, and both write and commit take batch
60+ writes only, since there is no snapshot to record a commit identifier or a watermark in.
61+ ``TableScan `` takes a partition filter and a limit; a predicate or a bucket filter is refused. Java
62+ refuses a predicate from ``FormatTableScan.withFilter `` too, but ``FormatReadBuilder.newScan() ``
63+ splits one first and hands the partition half to the scan, so there a predicate over partition
64+ columns still prunes directories. See the limits below.
65+
66+ Options given at the call win over the ones the schema stored, as they do for every other table -
67+ except ``type ``, which is structural and is read from the schema alone, so one read or write
68+ cannot decide what kind of table this is. The merged result is validated, not the schema's own
69+ options, so an option a format table refuses - ``metastore.partitioned-table ``, a file format
70+ nothing here can read - is refused wherever it comes from rather than dropped in silence.
71+
72+ A setting the format path cannot act on is refused by name rather than quietly dropped:
73+
74+ * ``ReadContextBuilder::SetReadSchema() ``. A projected read schema can rename a column, prune a
75+ nested one and give it metadata of its own; a format table's projection is a list of top-level
76+ names, so name the columns instead.
77+ * ``WithStreamingMode() `` on a scan or a write, a global index result on a scan, and a real-time
78+ context on a scan, a read or a write: a format table has no snapshots, no real-time store and no
79+ index.
80+ * ``WriteContextBuilder::WithWriteSchema() ``, which names a subset of the columns to write.
81+ * ``WriteContextBuilder::WithWriteId() ``, which prefixes a postpone-bucket writer's files so one
82+ compaction reader can put them back in order; a format table has no buckets.
83+ * ``CommitContextBuilder::IgnoreEmptyCommit(false) ``, ``UseRESTCatalogCommit(true) `` and
84+ ``AppendCommitCheckConflict(true) ``. Keeping an empty commit means writing a snapshot that adds
85+ no files, a rest-catalog commit sends that snapshot to a catalog, and the conflict check reads
86+ the manifests of concurrent commits - none of which exist here. Each is refused only when set
87+ away from its default, so an ordinary commit is unaffected.
88+ * A scan predicate or bucket filter, as above, and more than one partition filter: a scan descends
89+ one directory layout, so it takes the values of a single partition rather than a set of them.
90+
91+ What a data file is opened with is not one of the refusals. ``EnablePrefetch() ``, the read-ahead
92+ cache and its ``CacheConfig ``, and the ``Cache `` a read carries all apply, because a format table
93+ opens its files through the same component the managed table path opens its own with. What differs
94+ between the two paths is which files there are and how a row is put back together, not how a file
95+ is read.
96+
97+ Some settings are not refused because they describe machinery the format path never reaches, and
98+ refusing them would refuse the defaults: ``EnableMultiThreadRowToBatch() `` on a read, a write's
99+ temporary directory and spill configuration, and ``WithIgnoreNumBucketCheck() `` and
100+ ``WithIgnorePreviousFiles() `` on a write. They have no effect here: a format read hands out the
101+ batches parquet or orc already produced rather than assembling them from rows, a format write
102+ buffers in memory and never spills, and a table with no buckets has no bucket count to check and
103+ no previous files to read back.
104+
105+ A write is two-phase, since a directory has no metadata to switch atomically: a file is written
106+ into a ``_temporary `` directory beside where it will end up, under a hidden name of its own, and
107+ only the commit renames it into place. That is the layout Java Paimon's
108+ ``RenamingTwoPhaseOutputStream `` stages under. The directory and the name are both hidden, the
109+ convention a Hive-style directory uses for output that is not committed table data, and what a scan
110+ of this table skips. The ``_temporary `` directory is shared with every other writer
111+ of the same table and is left behind after a commit.
112+
113+ A plan is in-memory only. ``FormatDataSplit `` has no serialized form - ``Split::Serialize() ``
114+ refuses it - and neither has ``FormatCommitMessage ``: a format table's plan has no cross-runtime
115+ encoding, so plan, read and commit within one process.
48116
49117A ``FormatTableWrite `` and a ``FormatTableCommit `` are each driven by one thread, but separate
50118ones may fill and add to a table at once: each write stages its files under a uuid of its own, and
@@ -65,15 +133,20 @@ closer to the target.
65133Aborting a write
66134----------------
67135``FormatTableWrite::Abort() `` removes the files the write staged. It is the one call still allowed
68- after ``PrepareCommit() ``, so a commit that is prepared and then abandoned can still be cleaned up.
136+ after ``PrepareCommit() ``, and that is what it is for: a write dropped *before * preparing clears
137+ its staged files from its own destructor, so only a commit that is prepared and then abandoned
138+ needs it.
69139
70140Path containment is checked on the path text, which stops a ``.. `` from leaving the table but not
71141a symbolic link pointing out of it - the same as Java's own local file system behaviour.
72142
73143``FormatTableCommit::Abort() `` does the same for the messages a commit was given. **Neither undoes
74144a commit that succeeded **: once a file has been renamed into place it is no longer staged, and
75- nothing here will take it back. Both are best effort and never fail, so a warning in the log is the
76- only signal that a file could not be removed.
145+ nothing here will take it back. Java's committer removes the published path as well as the staged
146+ one, which matters there because a commit publishes file by file with nothing watching; here a
147+ commit that fails part way takes its own published files back before it returns, so an abort is
148+ left with the staged files alone. Both are best effort and never fail, so a warning in the log is
149+ the only signal that a file could not be removed.
77150
78151Give ``FormatTableCommit `` only the messages this job's own writers produced. A message names a
79152staged file by path, and a commit can tell that the path belongs to this table, sits in the
@@ -102,14 +175,32 @@ instead. The one exception is the value standing for a null partition, ``partiti
102175which the scan reads at a partition level by design. Under the ``key=value `` layout the question
103176does not arise, since the key in front of the value keeps the directory name visible.
104177
105- Two smaller differences come from this library's own conventions:
178+ A few smaller differences come from this library's own conventions:
106179
107180* a write takes one partition per batch: the batch declares it through
108181 ``RecordBatch::SetPartition() ``, every row is checked against that declaration, and a batch
109182 mixing partitions is refused. Java routes row by row, so one write call there may land in any
110183 number of partitions;
184+ * a write takes its partition from ``RecordBatch::SetPartition() `` rather than from the rows, so
185+ the values arrive as text. They are still read into their column types and rendered back out
186+ before anything is named after them - the round trip Java's writer makes when it renders a
187+ partition out of the row it is writing, through the partition computer its
188+ ``FileStorePathFactory `` holds. The table therefore decides the directory name and the commit
189+ message, not the spelling the caller used: with ``partition.legacy-name `` on, its default, a
190+ ``DATE `` partition is written as its day count whether the caller wrote ``19723 `` or
191+ ``2024-01-01 ``, and as ``YYYY-MM-DD `` when the option is off. A value that cannot be read into
192+ its column type is refused. ``FormatTableCommit ``'s static partition is *not * put through that
193+ round trip and is used as given, which is what Java's ``FormatTableCommit.buildPartitionPath ``
194+ does with it too;
195+ * a commit message carries the partition its file belongs to, and a commit checks that it agrees
196+ with the directory the file sits in. Java's message carries none and derives the partition from
197+ the committer's target path, so the two cannot disagree there. A message here is a public struct
198+ a caller may have built itself, so the value is checked rather than trusted;
111199* a projection that names the same column twice is rejected when the read is built. Java reads
112- such a column once per entry.
200+ such a column once per entry;
201+ * a row limit is the caller's: ``FormatTableScan `` takes one so that a plan can drop splits it
202+ cannot need, but ``FormatTableRead `` does not bound the reader it hands out, and the caller stops
203+ calling ``NextBatch() `` once it has enough. Java wraps its reader in a ``LimitRecordReader ``.
113204
114205Current limits
115206--------------
@@ -129,10 +220,13 @@ Compared with Java Paimon, this implementation does not yet support:
129220 Partition discovery here also lists one directory level at a time and applies the filter to each
130221 name, while Java turns a leading run of equality constraints into a path and starts listing
131222 below it; a table with many partitions therefore costs more listings here than in Java;
132- * ``scan.ignore-corrupt-file `` and ``scan.ignore-lost-file ``, which are not implemented: a
223+ * ``scan.ignore-corrupt-files `` and ``scan.ignore-lost-files ``, which are not implemented: a
133224 corrupt or missing data file fails the read rather than being skipped;
134- * ``partition.legacy-name ``, which changes how a partition value is rendered into its directory
135- name;
225+ * ``dynamic-partition-overwrite ``. An overwrite here always replaces the partitions the commit
226+ actually writes to, which is what Java does under that option's default of ``true ``. Java also
227+ has the other mode: with it off, and always for an unpartitioned table, an overwrite empties
228+ everything the table holds - so a statement whose query returns nothing still clears the table.
229+ A commit with no messages therefore clears nothing here where Java would;
136230* ``format-table.commit-hive-sync-url ``, which registers committed partitions with a Hive
137231 metastore;
138232* column default values. Java replaces a null in a column whose schema field declares a default
@@ -141,8 +235,12 @@ Compared with Java Paimon, this implementation does not yet support:
141235 out of what it writes, leaving files that carry nothing but a row count; here such a schema is
142236 refused when the table is created and when it is opened;
143237* ``TIMESTAMP ``, ``DECIMAL ``, ``FLOAT `` and ``DOUBLE `` partition columns, which Java allows. This
144- is a restriction of the whole library rather than of format tables, and a table Java created
145- with such a partition column fails to open here rather than at the first read.
238+ is a restriction of the whole library rather than of format tables. The types that do work are
239+ ``BOOLEAN ``, ``TINYINT ``, ``SMALLINT ``, ``INT ``, ``BIGINT ``, ``STRING `` and ``DATE `` - the set
240+ the managed table path reads and writes partitions in. Any other, ``BINARY `` among them, is
241+ refused when the table is created and when it is opened, rather than at the first read or write:
242+ validation asks by building the partition computer that does the round trip, so there is one
243+ answer rather than a list of types that could fall out of step with it.
146244
147245``data-file.path-directory `` has no effect here, and none in Java either: Java's format table
148246writer builds its paths from the table root rather than from that directory.
0 commit comments