@@ -307,3 +307,217 @@ If you want to achieve the best compression, you should use `GZIP` or `SNAPPY` w
307307
308308For not yet supported algorithms, please check our [ Roadmap] ( https://github.com/orgs/flow-php/projects/1 ) to understand when they will be supported.
309309
310+ ## Column Encodings
311+
312+ Parquet supports various column encoding algorithms that can significantly impact file size and query performance.
313+ You can specify custom encodings for individual columns using flat path notation.
314+
315+ ### Available Encodings
316+
317+ #### PLAIN
318+ The default encoding that stores values as-is without any compression scheme.
319+
320+ ** When to use:**
321+ - Small datasets where compression overhead isn't justified
322+ - Columns with high cardinality and random distribution
323+ - When you need maximum compatibility with other Parquet readers
324+
325+ ** Supported types:** All column types
326+
327+ ``` php
328+ use Flow\Parquet\Options;
329+ use Flow\Parquet\Option;
330+
331+ $options = Options::default()->set(Option::COLUMNS_ENCODINGS, [
332+ 'description' => 'PLAIN',
333+ 'uuid' => 'PLAIN'
334+ ]);
335+ ```
336+
337+ #### RLE_DICTIONARY
338+ Run Length Encoding with Dictionary compression. Values are stored in a dictionary and replaced with indices.
339+
340+ ** When to use:**
341+ - Columns with low cardinality (many repeated values)
342+ - String columns with repeated categories (status, country, department)
343+ - Enumeration-like data
344+ - Significant file size reduction (often 50-90% smaller)
345+
346+ ** Supported types:** All types except ` FIXED_LEN_BYTE_ARRAY `
347+
348+ ``` php
349+ $options = Options::default()->set(Option::COLUMNS_ENCODINGS, [
350+ 'status' => 'RLE_DICTIONARY', // 'active', 'inactive', 'pending'
351+ 'country_code' => 'RLE_DICTIONARY', // 'US', 'UK', 'DE', 'FR'
352+ 'department' => 'RLE_DICTIONARY' // 'engineering', 'sales', 'marketing'
353+ ]);
354+ ```
355+
356+ #### DELTA_BINARY_PACKED
357+ Delta encoding with binary packing for integer columns. Stores differences between consecutive values.
358+
359+ ** When to use:**
360+ - Sequential or monotonic integer data (IDs, timestamps, counters)
361+ - Time series data with incremental values
362+ - Can achieve 70-95% compression for sequential data
363+
364+ ** Supported types:** Only ` INT32 ` and ` INT64 `
365+
366+ ``` php
367+ $options = Options::default()->set(Option::COLUMNS_ENCODINGS, [
368+ 'user_id' => 'DELTA_BINARY_PACKED', // 1, 2, 3, 4, 5...
369+ 'timestamp_ms' => 'DELTA_BINARY_PACKED', // 1634567890123, 1634567890124...
370+ 'order_number' => 'DELTA_BINARY_PACKED' // Sequential order IDs
371+ ]);
372+ ```
373+
374+ ### Using Custom Encodings
375+
376+ #### Basic Column Encoding
377+
378+ ``` php
379+ use Flow\Parquet\{Writer, Options, Option};
380+ use Flow\Parquet\ParquetFile\Schema;
381+ use Flow\Parquet\ParquetFile\Schema\FlatColumn;
382+
383+ $schema = Schema::with(
384+ FlatColumn::int64('user_id'),
385+ FlatColumn::string('status'),
386+ FlatColumn::string('description')
387+ );
388+
389+ $options = Options::default()->set(Option::COLUMNS_ENCODINGS, [
390+ 'user_id' => 'DELTA_BINARY_PACKED', // Sequential IDs
391+ 'status' => 'RLE_DICTIONARY', // Limited set of values
392+ 'description' => 'PLAIN' // High variance text
393+ ]);
394+
395+ $writer = new Writer(compressions: Compressions::SNAPPY, options: $options);
396+ ```
397+
398+ #### Nested Column Encoding (Flat Path Notation)
399+
400+ For nested structures, use dot notation to specify the exact column path.
401+ The flat path follows Parquet's internal structure conventions:
402+
403+ ** Flat Path Patterns:**
404+ - ** Struct fields** : ` parent.field_name `
405+ - ** List elements** : ` list_name.list.element `
406+ - ** Map keys** : ` map_name.key_value.key `
407+ - ** Map values** : ` map_name.key_value.value `
408+
409+ ``` php
410+ use Flow\Parquet\ParquetFile\Schema\{NestedColumn, ListElement, MapKey, MapValue};
411+
412+ $schema = Schema::with(
413+ NestedColumn::struct('user', [
414+ FlatColumn::int64('id'),
415+ FlatColumn::string('name'),
416+ NestedColumn::struct('address', [
417+ FlatColumn::string('street'),
418+ FlatColumn::string('city'),
419+ FlatColumn::string('country')
420+ ])
421+ ]),
422+ NestedColumn::list('tags', ListElement::string()),
423+ NestedColumn::map('metadata', MapKey::string(), MapValue::string())
424+ );
425+ ```
426+
427+ ** Understanding Flat Paths:**
428+
429+ ``` php
430+ // STRUCT: Direct field access with dot notation
431+ 'user.id' // user struct → id field
432+ 'user.name' // user struct → name field
433+ 'user.address.street' // user struct → address struct → street field
434+ 'user.address.city' // user struct → address struct → city field
435+ 'user.address.country' // user struct → address struct → country field
436+
437+ // LIST: Always includes intermediate '.list.element' structure
438+ 'tags.list.element' // tags list → list wrapper → element (the actual string values)
439+
440+ // MAP: Always includes intermediate '.key_value' structure
441+ 'metadata.key_value.key' // metadata map → key_value wrapper → key (string keys)
442+ 'metadata.key_value.value' // metadata map → key_value wrapper → value (string values)
443+ ```
444+
445+ ** Applying Custom Encodings:**
446+
447+ ``` php
448+ $options = Options::default()->set(Option::COLUMNS_ENCODINGS, [
449+ // Struct fields - direct access
450+ 'user.id' => 'DELTA_BINARY_PACKED',
451+ 'user.name' => 'RLE_DICTIONARY',
452+ 'user.address.street' => 'PLAIN',
453+ 'user.address.city' => 'RLE_DICTIONARY',
454+ 'user.address.country' => 'RLE_DICTIONARY',
455+
456+ // List elements - note the '.list.element' suffix
457+ 'tags.list.element' => 'RLE_DICTIONARY',
458+
459+ // Map key/value pairs - note the '.key_value.key/value' suffix
460+ 'metadata.key_value.key' => 'RLE_DICTIONARY',
461+ 'metadata.key_value.value' => 'PLAIN'
462+ ]);
463+ ```
464+
465+ ** Complex Nested Example:**
466+
467+ ``` php
468+ // Complex nested structure with lists of structs and maps
469+ $schema = Schema::with(
470+ NestedColumn::list('orders', ListElement::structure([
471+ FlatColumn::int64('order_id'),
472+ FlatColumn::string('status'),
473+ NestedColumn::map('attributes', MapKey::string(), MapValue::string())
474+ ]))
475+ );
476+
477+ $options = Options::default()->set(Option::COLUMNS_ENCODINGS, [
478+ // List of structs: list_name.list.element.field_name
479+ 'orders.list.element.order_id' => 'DELTA_BINARY_PACKED',
480+ 'orders.list.element.status' => 'RLE_DICTIONARY',
481+
482+ // Map inside list element: list_name.list.element.map_name.key_value.key/value
483+ 'orders.list.element.attributes.key_value.key' => 'RLE_DICTIONARY',
484+ 'orders.list.element.attributes.key_value.value' => 'PLAIN'
485+ ]);
486+ ```
487+
488+ #### Mixed Encoding Strategy
489+
490+ ``` php
491+ $options = Options::default()->set(Option::COLUMNS_ENCODINGS, [
492+ // High cardinality sequential data
493+ 'order_id' => 'DELTA_BINARY_PACKED',
494+ 'created_timestamp' => 'DELTA_BINARY_PACKED',
495+
496+ // Low cardinality categorical data
497+ 'order_status' => 'RLE_DICTIONARY',
498+ 'payment_method' => 'RLE_DICTIONARY',
499+ 'shipping_country' => 'RLE_DICTIONARY',
500+
501+ // High variance descriptive data
502+ 'customer_notes' => 'PLAIN',
503+ 'product_description' => 'PLAIN'
504+ ]);
505+ ```
506+
507+ ### Encoding Compatibility
508+
509+ | Encoding | INT32/INT64 | BYTE_ARRAY | BOOLEAN | FLOAT/DOUBLE | FIXED_LEN_BYTE_ARRAY |
510+ | ----------| -------------| ------------| ---------| --------------| ----------------------|
511+ | PLAIN | ✅ | ✅ | ✅ | ✅ | ✅ |
512+ | RLE_DICTIONARY | ✅ | ✅ | ✅ | ✅ | ❌ |
513+ | DELTA_BINARY_PACKED | ✅ | ❌ | ❌ | ❌ | ❌ |
514+
515+ ### Performance Guidelines
516+
517+ 1 . ** Analyze your data first** - Check cardinality and distribution patterns
518+ 2 . ** Use RLE_DICTIONARY for categorical data** - Countries, statuses, departments
519+ 3 . ** Use DELTA_BINARY_PACKED for sequential integers** - IDs, timestamps, counters
520+ 4 . ** Use PLAIN for high-variance data** - Descriptions, UUIDs, random data
521+ 5 . ** Test different combinations** - Measure file size and query performance
522+ 6 . ** Consider query patterns** - Frequently filtered columns benefit from dictionary encoding
523+
0 commit comments