summaryrefslogtreecommitdiffstats
path: root/src/arrow/docs/source/cpp/parquet.rst
blob: 88ea4e5b6c8d1b608e674da50dbbb95213b730df (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
.. Licensed to the Apache Software Foundation (ASF) under one
.. or more contributor license agreements.  See the NOTICE file
.. distributed with this work for additional information
.. regarding copyright ownership.  The ASF licenses this file
.. to you under the Apache License, Version 2.0 (the
.. "License"); you may not use this file except in compliance
.. with the License.  You may obtain a copy of the License at

..   http://www.apache.org/licenses/LICENSE-2.0

.. Unless required by applicable law or agreed to in writing,
.. software distributed under the License is distributed on an
.. "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
.. KIND, either express or implied.  See the License for the
.. specific language governing permissions and limitations
.. under the License.

.. default-domain:: cpp
.. highlight:: cpp

.. cpp:namespace:: parquet

=================================
Reading and writing Parquet files
=================================

.. seealso::
   :ref:`Parquet reader and writer API reference <cpp-api-parquet>`.

The `Parquet format <https://parquet.apache.org/documentation/latest/>`__
is a space-efficient columnar storage format for complex data.  The Parquet
C++ implementation is part of the Apache Arrow project and benefits
from tight integration with the Arrow C++ classes and facilities.

Supported Parquet features
==========================

The Parquet format has many features, and Parquet C++ supports a subset of them.

Page types
----------

+-------------------+---------+
| Page type         | Notes   |
+===================+=========+
| DATA_PAGE         |         |
+-------------------+---------+
| DATA_PAGE_V2      |         |
+-------------------+---------+
| DICTIONARY_PAGE   |         |
+-------------------+---------+

*Unsupported page type:* INDEX_PAGE. When reading a Parquet file, pages of
this type are ignored.

Compression
-----------

+-------------------+---------+
| Compression codec | Notes   |
+===================+=========+
| SNAPPY            |         |
+-------------------+---------+
| GZIP              |         |
+-------------------+---------+
| BROTLI            |         |
+-------------------+---------+
| LZ4               | \(1)    |
+-------------------+---------+
| ZSTD              |         |
+-------------------+---------+

* \(1) On the read side, Parquet C++ is able to decompress both the regular
  LZ4 block format and the ad-hoc Hadoop LZ4 format used by the
  `reference Parquet implementation <https://github.com/apache/parquet-mr>`__.
  On the write side, Parquet C++ always generates the ad-hoc Hadoop LZ4 format.

*Unsupported compression codec:* LZO.

Encodings
---------

+--------------------------+---------+
| Encoding                 | Notes   |
+==========================+=========+
| PLAIN                    |         |
+--------------------------+---------+
| PLAIN_DICTIONARY         |         |
+--------------------------+---------+
| BIT_PACKED               |         |
+--------------------------+---------+
| RLE                      | \(1)    |
+--------------------------+---------+
| RLE_DICTIONARY           | \(2)    |
+--------------------------+---------+
| BYTE_STREAM_SPLIT        |         |
+--------------------------+---------+

* \(1) Only supported for encoding definition and repetition levels, not values.

* \(2) On the write path, RLE_DICTIONARY is only enabled if Parquet format version
  2.4 or greater is selected in :func:`WriterProperties::version`.

*Unsupported encodings:* DELTA_BINARY_PACKED, DELTA_LENGTH_BYTE_ARRAY,
DELTA_BYTE_ARRAY.

Types
-----

Physical types
~~~~~~~~~~~~~~

+--------------------------+-------------------------+------------+
| Physical type            | Mapped Arrow type       | Notes      |
+==========================+=========================+============+
| BOOLEAN                  | Boolean                 |            |
+--------------------------+-------------------------+------------+
| INT32                    | Int32 / other           | \(1)       |
+--------------------------+-------------------------+------------+
| INT64                    | Int64 / other           | \(1)       |
+--------------------------+-------------------------+------------+
| INT96                    | Timestamp (nanoseconds) | \(2)       |
+--------------------------+-------------------------+------------+
| FLOAT                    | Float32                 |            |
+--------------------------+-------------------------+------------+
| DOUBLE                   | Float64                 |            |
+--------------------------+-------------------------+------------+
| BYTE_ARRAY               | Binary / other          | \(1) \(3)  |
+--------------------------+-------------------------+------------+
| FIXED_LENGTH_BYTE_ARRAY  | FixedSizeBinary / other | \(1)       |
+--------------------------+-------------------------+------------+

* \(1) Can be mapped to other Arrow types, depending on the logical type
  (see below).

* \(2) On the write side, :func:`ArrowWriterProperties::support_deprecated_int96_timestamps`
  must be enabled.

* \(3) On the write side, an Arrow LargeBinary can also mapped to BYTE_ARRAY.

Logical types
~~~~~~~~~~~~~

Specific logical types can override the default Arrow type mapping for a given
physical type.

+-------------------+-----------------------------+----------------------------+---------+
| Logical type      | Physical type               | Mapped Arrow type          | Notes   |
+===================+=============================+============================+=========+
| NULL              | Any                         | Null                       | \(1)    |
+-------------------+-----------------------------+----------------------------+---------+
| INT               | INT32                       | Int8 / UInt8 / Int16 /     |         |
|                   |                             | UInt16 / Int32 / UInt32    |         |
+-------------------+-----------------------------+----------------------------+---------+
| INT               | INT64                       | Int64 / UInt64             |         |
+-------------------+-----------------------------+----------------------------+---------+
| DECIMAL           | INT32 / INT64 / BYTE_ARRAY  | Decimal128 / Decimal256    | \(2)    |
|                   | / FIXED_LENGTH_BYTE_ARRAY   |                            |         |
+-------------------+-----------------------------+----------------------------+---------+
| DATE              | INT32                       | Date32                     | \(3)    |
+-------------------+-----------------------------+----------------------------+---------+
| TIME              | INT32                       | Time32 (milliseconds)      |         |
+-------------------+-----------------------------+----------------------------+---------+
| TIME              | INT64                       | Time64 (micro- or          |         |
|                   |                             | nanoseconds)               |         |
+-------------------+-----------------------------+----------------------------+---------+
| TIMESTAMP         | INT64                       | Timestamp (milli-, micro-  |         |
|                   |                             | or nanoseconds)            |         |
+-------------------+-----------------------------+----------------------------+---------+
| STRING            | BYTE_ARRAY                  | Utf8                       | \(4)    |
+-------------------+-----------------------------+----------------------------+---------+
| LIST              | Any                         | List                       | \(5)    |
+-------------------+-----------------------------+----------------------------+---------+
| MAP               | Any                         | Map                        | \(6)    |
+-------------------+-----------------------------+----------------------------+---------+

* \(1) On the write side, the Parquet physical type INT32 is generated.

* \(2) On the write side, a FIXED_LENGTH_BYTE_ARRAY is always emitted.

* \(3) On the write side, an Arrow Date64 is also mapped to a Parquet DATE INT32.

* \(4) On the write side, an Arrow LargeUtf8 is also mapped to a Parquet STRING.

* \(5) On the write side, an Arrow LargeList or FixedSizedList is also mapped to
  a Parquet LIST.

* \(6) On the read side, a key with multiple values does not get deduplicated,
  in contradiction with the
  `Parquet specification <https://github.com/apache/parquet-format/blob/master/LogicalTypes.md#maps>`__.

*Unsupported logical types:* JSON, BSON, UUID.  If such a type is encountered
when reading a Parquet file, the default physical type mapping is used (for
example, a Parquet JSON column may be read as Arrow Binary or FixedSizeBinary).

Converted types
~~~~~~~~~~~~~~~

While converted types are deprecated in the Parquet format (they are superceded
by logical types), they are recognized and emitted by the Parquet C++
implementation so as to maximize compatibility with other Parquet
implementations.

Special cases
~~~~~~~~~~~~~

An Arrow Extension type is written out as its storage type.  It can still
be recreated at read time using Parquet metadata (see "Roundtripping Arrow
types" below).

An Arrow Dictionary type is written out as its value type.  It can still
be recreated at read time using Parquet metadata (see "Roundtripping Arrow
types" below).

Roundtripping Arrow types
~~~~~~~~~~~~~~~~~~~~~~~~~

While there is no bijection between Arrow types and Parquet types, it is
possible to serialize the Arrow schema as part of the Parquet file metadata.
This is enabled using :func:`ArrowWriterProperties::store_schema`.

On the read path, the serialized schema will be automatically recognized
and will recreate the original Arrow data, converting the Parquet data as
required (for example, a LargeList will be recreated from the Parquet LIST
type).

As an example, when serializing an Arrow LargeList to Parquet:

* The data is written out as a Parquet LIST

* When read back, the Parquet LIST data is decoded as an Arrow LargeList if
  :func:`ArrowWriterProperties::store_schema` was enabled when writing the file;
  otherwise, it is decoded as an Arrow List.

Serialization details
"""""""""""""""""""""

The Arrow schema is serialized as a :ref:`Arrow IPC <format-ipc>` schema message,
then base64-encoded and stored under the ``ARROW:schema`` metadata key in
the Parquet file metadata.

Limitations
~~~~~~~~~~~

Writing or reading back FixedSizedList data with null entries is not supported.

Encryption
----------

Parquet C++ implements all features specified in the
`encryption specification <https://github.com/apache/parquet-format/blob/master/Encryption.md>`__,
except for encryption of column index and bloom filter modules. 

More specifically, Parquet C++ supports:

* AES_GCM_V1 and AES_GCM_CTR_V1 encryption algorithms.
* AAD suffix for Footer, ColumnMetaData, Data Page, Dictionary Page,
  Data PageHeader, Dictionary PageHeader module types. Other module types
  (ColumnIndex, OffsetIndex, BloomFilter Header, BloomFilter Bitset) are not
  supported.
* EncryptionWithFooterKey and EncryptionWithColumnKey modes.
* Encrypted Footer and Plaintext Footer modes.


Reading Parquet files
=====================

The :class:`arrow::FileReader` class reads data for an entire
file or row group into an :class:`::arrow::Table`.

The :class:`StreamReader` and :class:`StreamWriter` classes allow for
data to be written using a C++ input/output streams approach to
read/write fields column by column and row by row.  This approach is
offered for ease of use and type-safety.  It is of course also useful
when data must be streamed as files are read and written
incrementally.

Please note that the performance of the :class:`StreamReader` and
:class:`StreamWriter` classes will not be as good due to the type
checking and the fact that column values are processed one at a time.

FileReader
----------

The Parquet :class:`arrow::FileReader` requires a
:class:`::arrow::io::RandomAccessFile` instance representing the input
file.

.. code-block:: cpp

   #include "arrow/parquet/arrow/reader.h"

   {
      // ...
      arrow::Status st;
      arrow::MemoryPool* pool = default_memory_pool();
      std::shared_ptr<arrow::io::RandomAccessFile> input = ...;

      // Open Parquet file reader
      std::unique_ptr<parquet::arrow::FileReader> arrow_reader;
      st = parquet::arrow::OpenFile(input, pool, &arrow_reader);
      if (!st.ok()) {
         // Handle error instantiating file reader...
      }

      // Read entire file as a single Arrow table
      std::shared_ptr<arrow::Table> table;
      st = arrow_reader->ReadTable(&table);
      if (!st.ok()) {
         // Handle error reading Parquet data...
      }
   }

Finer-grained options are available through the
:class:`arrow::FileReaderBuilder` helper class.

.. TODO write section about performance and memory efficiency

StreamReader
------------

The :class:`StreamReader` allows for Parquet files to be read using
standard C++ input operators which ensures type-safety.

Please note that types must match the schema exactly i.e. if the
schema field is an unsigned 16-bit integer then you must supply a
uint16_t type.

Exceptions are used to signal errors.  A :class:`ParquetException` is
thrown in the following circumstances:

* Attempt to read field by supplying the incorrect type.

* Attempt to read beyond end of row.

* Attempt to read beyond end of file.

.. code-block:: cpp

   #include "arrow/io/file.h"
   #include "parquet/stream_reader.h"

   {
      std::shared_ptr<arrow::io::ReadableFile> infile;

      PARQUET_ASSIGN_OR_THROW(
         infile,
         arrow::io::ReadableFile::Open("test.parquet"));

      parquet::StreamReader os{parquet::ParquetFileReader::Open(infile)};

      std::string article;
      float price;
      uint32_t quantity;

      while ( !os.eof() )
      {
         os >> article >> price >> quantity >> parquet::EndRow;
         // ...
      }
   }

Writing Parquet files
=====================

WriteTable
----------

The :func:`arrow::WriteTable` function writes an entire
:class:`::arrow::Table` to an output file.

.. code-block:: cpp

   #include "parquet/arrow/writer.h"

   {
      std::shared_ptr<arrow::io::FileOutputStream> outfile;
      PARQUET_ASSIGN_OR_THROW(
         outfile,
         arrow::io::FileOutputStream::Open("test.parquet"));

      PARQUET_THROW_NOT_OK(
         parquet::arrow::WriteTable(table, arrow::default_memory_pool(), outfile, 3));
   }

StreamWriter
------------

The :class:`StreamWriter` allows for Parquet files to be written using
standard C++ output operators.  This type-safe approach also ensures
that rows are written without omitting fields and allows for new row
groups to be created automatically (after certain volume of data) or
explicitly by using the :type:`EndRowGroup` stream modifier.

Exceptions are used to signal errors.  A :class:`ParquetException` is
thrown in the following circumstances:

* Attempt to write a field using an incorrect type.

* Attempt to write too many fields in a row.

* Attempt to skip a required field.

.. code-block:: cpp

   #include "arrow/io/file.h"
   #include "parquet/stream_writer.h"

   {
      std::shared_ptr<arrow::io::FileOutputStream> outfile;

      PARQUET_ASSIGN_OR_THROW(
         outfile,
         arrow::io::FileOutputStream::Open("test.parquet"));

      parquet::WriterProperties::Builder builder;
      std::shared_ptr<parquet::schema::GroupNode> schema;

      // Set up builder with required compression type etc.
      // Define schema.
      // ...

      parquet::StreamWriter os{
         parquet::ParquetFileWriter::Open(outfile, schema, builder.build())};

      // Loop over some data structure which provides the required
      // fields to be written and write each row.
      for (const auto& a : getArticles())
      {
         os << a.name() << a.price() << a.quantity() << parquet::EndRow;
      }
   }