vbhanuchander-lang opened a new pull request, #8254:
URL: https://github.com/apache/hop/pull/8254

   Closes #8080.
   
   Brings Hive-style partitioned writes to the standard **Parquet File Output** 
transform, so the
   layout that Native Spark mode can already produce no longer requires the 
Spark engine.
   
   ### What it does
   
   Configure one or more incoming fields as partition fields and the transform 
writes
   
   ```
   /datalake/sales/year=2026/month=08/part-00-0000.parquet
   /datalake/sales/year=2026/month=09/part-00-0001.parquet
   ```
   
   instead of a single file set. With no partition fields configured the 
transform behaves exactly as
   before — same single file, same name, same columns.
   
   ### Design decisions worth a reviewer's attention
   
   **Partition columns are not written into the files.** This matches 
`DataFrameWriter.partitionBy`,
   which is what the issue asks for parity with, and it is what a reader 
expects when it recovers the
   column from the folder name. Configuring *every* field as a partition field 
is rejected rather than
   producing empty files.
   
   **Two things surfaced while testing that are worth calling out, because 
neither is obvious.**
   
   *HopVfs percent-decodes the path it is given.* Writing `region=EU%2FWest` 
produced
   `region=EU/West/` on disk — two folder levels — because VFS resolves the 
path as a URI. The Hive
   convention still wants `%2F` in the folder name, so the partition key keeps 
its Hive-style form and
   only the string handed to VFS has its `%` doubled to `%25`; VFS decodes that 
back to a literal `%`.
   A literal `%` in a value is escaped as `%25` for the same round-trip reason.
   
   *A per-run token in the file name.* Without it, two runs in Append mode both 
wrote
   `part-00-0000.parquet` and the second silently replaced the first — which is 
the opposite of
   appending. Each run now contributes eight hex characters to the name.
   
   **Nulls and awkward values follow the Hive convention.** A null or empty 
partition value becomes
   `__HIVE_DEFAULT_PARTITION__`, the same sentinel Hive and Spark use, so the 
output stays readable by
   them. Values are percent-escaped for the characters that would otherwise 
break the layout — most
   importantly `/`, so `region=EU/West` produces one folder named 
`region=EU%2FWest` rather than two
   levels.
   
   **Write modes** (`Existing data` in the dialog) decide what happens to data 
already in a partition
   folder, and only apply while partitioning:
   
   | Mode | Behaviour |
   |---|---|
   | Append *(default)* | Leave existing files, add new ones. Same as the 
pre-partitioning behaviour. |
   | Overwrite partitions | Empty a partition folder before this run's first 
file goes into it. Untouched partitions are left alone. |
   | Fail if exists | Refuse to write if the partition folder already exists. |
   | Overwrite all | Empty the whole base folder once, before the first file is 
written. |
   
   The subtle one is *Overwrite partitions*: it must clear a folder **once per 
run**, not once per
   file. A partition that gets closed and reopened (see below) would otherwise 
delete the rows the same
   run had just written to it. There is a test for exactly that.
   
   **Bounded memory: `Maximum open partitions` (default 10).** Every open 
Parquet writer buffers up to
   a full row group — the row group size defaults to 256 MB — so one writer per 
distinct partition
   value would run a pipeline out of memory on a wide key. Spark avoids this by 
sorting; a streaming
   transform cannot, so instead the transform keeps at most N writers open and 
closes the
   least-recently-written one when it needs another. Reopening a partition 
starts a new `part-...` file,
   which is why a partition can end up with several files. The row-count split 
(`Split into parts`)
   applies per partition file as well.
   
   ### Structure
   
   The partitioned path is additive: `processRow` branches on whether partition 
fields are configured,
   and the existing single-file code is untouched on the other side of that 
branch. Building the Avro
   and Parquet schema was extracted out of `openNewFile()` into `buildSchema()` 
so both paths share it
   without the partitioned path rebuilding it per file.
   
   ### Tests
   
   `ParquetOutputPartitionTest`, 16 tests (the module is 47 in total, all 
passing), covering the path building and escaping, the field
   exclusion and its three rejection cases, and end-to-end writes that assert 
the folder layout on disk
   and read the Parquet files back:
   
   - one folder per distinct value, and the partition column absent from the 
file's schema while both
     matching rows are present
   - a value containing `/` stays one folder level
   - each write mode, including *Overwrite partitions* leaving a sibling 
partition untouched, and not
     deleting what the same run just wrote (forced by setting the 
open-partition limit to 1)
   - six distinct values with a limit of two open partitions still writes every 
row
   - with no partition fields, still a single file with all three columns
   
   `./mvnw -pl plugins/tech/parquet -am test` is green, and `spotless:check` 
passes.
   
   ### One unrelated one-character fix
   
   `messages_en_US.properties` had `ParquetOutputDialog.RowGroupSize.Label Row 
group size` with no `=`,
   so that dialog label never resolved. Fixed while adding keys to the same 
file — happy to split it
   out if you would rather it went separately.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to