From 9f8ccb4d144431b53b0a1b088f075e0a380034b9 Mon Sep 17 00:00:00 2001 From: Arnav Balyan Date: Sat, 15 Aug 2026 16:02:01 +0530 Subject: [PATCH 1/2] update --- LICENSE.txt | 28 + cpp/src/arrow/vendored/fsst/LICENSE | 21 + cpp/src/arrow/vendored/fsst/README.md | 9 + cpp/src/arrow/vendored/fsst/fsst.h | 227 ++++ cpp/src/arrow/vendored/fsst/fsst_avx512.cpp | 149 ++ .../vendored/fsst/fsst_avx512_unroll1.inc | 57 + .../vendored/fsst/fsst_avx512_unroll2.inc | 114 ++ .../vendored/fsst/fsst_avx512_unroll3.inc | 171 +++ .../vendored/fsst/fsst_avx512_unroll4.inc | 228 ++++ cpp/src/arrow/vendored/fsst/libfsst.cpp | 651 +++++++++ cpp/src/arrow/vendored/fsst/libfsst.hpp | 471 +++++++ cpp/src/generated/parquet_types.cpp | 1202 ++++++++++------- cpp/src/generated/parquet_types.h | 102 +- cpp/src/generated/parquet_types.tcc | 556 +++++--- cpp/src/parquet/CMakeLists.txt | 21 + .../parquet/arrow/arrow_reader_writer_test.cc | 111 ++ cpp/src/parquet/column_page.h | 18 + cpp/src/parquet/column_reader.cc | 113 +- cpp/src/parquet/column_reader_test.cc | 63 + cpp/src/parquet/column_writer.cc | 147 +- cpp/src/parquet/column_writer.h | 6 + cpp/src/parquet/decoder.cc | 185 +++ cpp/src/parquet/encoder.cc | 209 +++ cpp/src/parquet/encoding.h | 23 + cpp/src/parquet/encoding_test.cc | 211 +++ cpp/src/parquet/file_deserialize_test.cc | 37 + cpp/src/parquet/file_reader.cc | 7 +- cpp/src/parquet/file_serialize_test.cc | 106 ++ cpp/src/parquet/fsst_internal.cc | 346 +++++ cpp/src/parquet/fsst_internal.h | 69 + cpp/src/parquet/meson.build | 3 + cpp/src/parquet/metadata.cc | 71 +- cpp/src/parquet/metadata.h | 14 + cpp/src/parquet/metadata_test.cc | 12 +- cpp/src/parquet/parquet.thrift | 28 + cpp/src/parquet/properties.cc | 5 + cpp/src/parquet/properties.h | 40 + cpp/src/parquet/properties_test.cc | 10 + cpp/src/parquet/thrift_internal.h | 13 + cpp/src/parquet/types.cc | 5 + cpp/src/parquet/types.h | 19 +- docs/source/cpp/parquet.rst | 7 + python/pyarrow/_parquet.pyx | 24 +- python/pyarrow/includes/libparquet.pxd | 5 + python/pyarrow/parquet/core.py | 3 +- python/pyarrow/tests/parquet/test_basic.py | 11 + python/pyarrow/tests/parquet/test_metadata.py | 21 + 47 files changed, 5184 insertions(+), 765 deletions(-) create mode 100644 cpp/src/arrow/vendored/fsst/LICENSE create mode 100644 cpp/src/arrow/vendored/fsst/README.md create mode 100644 cpp/src/arrow/vendored/fsst/fsst.h create mode 100644 cpp/src/arrow/vendored/fsst/fsst_avx512.cpp create mode 100644 cpp/src/arrow/vendored/fsst/fsst_avx512_unroll1.inc create mode 100644 cpp/src/arrow/vendored/fsst/fsst_avx512_unroll2.inc create mode 100644 cpp/src/arrow/vendored/fsst/fsst_avx512_unroll3.inc create mode 100644 cpp/src/arrow/vendored/fsst/fsst_avx512_unroll4.inc create mode 100644 cpp/src/arrow/vendored/fsst/libfsst.cpp create mode 100644 cpp/src/arrow/vendored/fsst/libfsst.hpp create mode 100644 cpp/src/parquet/fsst_internal.cc create mode 100644 cpp/src/parquet/fsst_internal.h diff --git a/LICENSE.txt b/LICENSE.txt index 5382ddfa5d1e..299b69593399 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -479,6 +479,34 @@ SOFTWARE. -------------------------------------------------------------------------------- +The files in cpp/src/arrow/vendored/fsst/ are from the Fast Static Symbol Table +(FSST) project (https://github.com/cwida/fsst) at commit +89f49c580c6388acf3b6ed2a49e1bfde6c05e616 and have the following license (MIT): + +MIT License + +Copyright (c) 2018-2020, CWI, TU Munich, FSU Jena + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- + cpp/src/arrow/util/logging.cc, cpp/src/arrow/util/logging.h and cpp/src/arrow/util/logging-test.cc are adapted from Ray Project (https://github.com/ray-project/ray) (Apache 2.0). diff --git a/cpp/src/arrow/vendored/fsst/LICENSE b/cpp/src/arrow/vendored/fsst/LICENSE new file mode 100644 index 000000000000..edb46d2db5fb --- /dev/null +++ b/cpp/src/arrow/vendored/fsst/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018-2020, CWI, TU Munich, FSU Jena + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/cpp/src/arrow/vendored/fsst/README.md b/cpp/src/arrow/vendored/fsst/README.md new file mode 100644 index 000000000000..13709b1979f6 --- /dev/null +++ b/cpp/src/arrow/vendored/fsst/README.md @@ -0,0 +1,9 @@ +# Vendored CWI FSST + +This directory contains the FSST8 implementation from +https://github.com/cwida/fsst at commit +`89f49c580c6388acf3b6ed2a49e1bfde6c05e616`. + +The sources are unchanged except for trailing-whitespace normalization. +Parquet-specific serialization and validation live in +`cpp/src/parquet/fsst_internal.cc`. diff --git a/cpp/src/arrow/vendored/fsst/fsst.h b/cpp/src/arrow/vendored/fsst/fsst.h new file mode 100644 index 000000000000..7ef79d20d1b6 --- /dev/null +++ b/cpp/src/arrow/vendored/fsst/fsst.h @@ -0,0 +1,227 @@ +/* + * the API for FSST compression -- (c) Peter Boncz, Viktor Leis and Thomas Neumann (CWI, TU Munich), 2018-2019 + * + * =================================================================================================================================== + * this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): + * + * Copyright 2018-2020, CWI, TU Munich, FSU Jena + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR + * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * You can contact the authors via the FSST source repository : https://github.com/cwida/fsst + * =================================================================================================================================== + * + * FSST: Fast Static Symbol Table compression + * see the paper https://github.com/cwida/fsst/raw/master/fsstcompression.pdf + * + * FSST is a compression scheme focused on string/text data: it can compress strings from distributions with many different values (i.e. + * where dictionary compression will not work well). It allows *random-access* to compressed data: it is not block-based, so individual + * strings can be decompressed without touching the surrounding data in a compressed block. When compared to e.g. lz4 (which is + * block-based), FSST achieves similar decompression speed, (2x) better compression speed and 30% better compression ratio on text. + * + * FSST encodes strings also using a symbol table -- but it works on pieces of the string, as it maps "symbols" (1-8 byte sequences) + * onto "codes" (single-bytes). FSST can also represent a byte as an exception (255 followed by the original byte). Hence, compression + * transforms a sequence of bytes into a (supposedly shorter) sequence of codes or escaped bytes. These shorter byte-sequences could + * be seen as strings again and fit in whatever your program is that manipulates strings. + * + * useful property: FSST ensures that strings that are equal, are also equal in their compressed form. + * + * In this API, strings are considered byte-arrays (byte = unsigned char) and a batch of strings is represented as an array of + * unsigned char* pointers to their starts. A seperate length array (of unsigned int) denotes how many bytes each string consists of. + * + * This representation as unsigned char* pointers tries to assume as little as possible on the memory management of the program + * that calls this API, and is also intended to allow passing strings into this API without copying (even if you use C++ strings). + * + * We optionally support C-style zero-terminated strings (zero appearing only at the end). In this case, the compressed strings are + * also zero-terminated strings. In zero-terminated mode, the zero-byte at the end *is* counted in the string byte-length. + */ +#ifndef FSST_INCLUDED_H +#define FSST_INCLUDED_H + +#ifdef _MSC_VER +#define __restrict__ +#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ +#define __ORDER_LITTLE_ENDIAN__ 2 +#include +static inline int __builtin_ctzl(unsigned long long x) { + unsigned long ret; + _BitScanForward64(&ret, x); + return (int)ret; +} +#endif + +#ifdef __cplusplus +#define FSST_FALLTHROUGH [[fallthrough]] +#include +extern "C" { +#else +#define FSST_FALLTHROUGH +#endif + +#include + +/* A compressed string is simply a string of 1-byte codes; except for code 255, which is followed by an uncompressed byte. */ +#define FSST_ESC 255 + +/* Data structure needed for compressing strings - use fsst_duplicate() to create thread-local copies. Use fsst_destroy() to free. */ +typedef void* fsst_encoder_t; /* opaque type - it wraps around a rather large (~900KB) C++ object */ + +/* Data structure needed for decompressing strings - read-only and thus can be shared between multiple decompressing threads. */ +typedef struct { + unsigned long long version; /* version id */ + unsigned char zeroTerminated; /* terminator is a single-byte code that does not appear in longer symbols */ + unsigned char len[255]; /* len[x] is the byte-length of the symbol x (1 < len[x] <= 8). */ + unsigned long long symbol[255]; /* symbol[x] contains in LITTLE_ENDIAN the bytesequence that code x represents (0 <= x < 255). */ +} fsst_decoder_t; + +/* Calibrate a FSST symboltable from a batch of strings (it is best to provide at least 16KB of data). */ +fsst_encoder_t* +fsst_create( + size_t n, /* IN: number of strings in batch to sample from. */ + const size_t lenIn[], /* IN: byte-lengths of the inputs */ + const unsigned char *strIn[], /* IN: string start pointers. */ + int zeroTerminated /* IN: whether input strings are zero-terminated. If so, encoded strings are as well (i.e. symbol[0]=""). */ +); + +/* Create another encoder instance, necessary to do multi-threaded encoding using the same symbol table. */ +fsst_encoder_t* +fsst_duplicate( + fsst_encoder_t *encoder /* IN: the symbol table to duplicate. */ +); + +#define FSST_MAXHEADER (8+1+8+2048+1) /* maxlen of deserialized fsst header, produced/consumed by fsst_export() resp. fsst_import() */ + +/* Space-efficient symbol table serialization (smaller than sizeof(fsst_decoder_t) - by saving on the unused bytes in symbols of len < 8). */ +unsigned int /* OUT: number of bytes written in buf, at most sizeof(fsst_decoder_t) */ +fsst_export( + fsst_encoder_t *encoder, /* IN: the symbol table to dump. */ + unsigned char *buf /* OUT: pointer to a byte-buffer where to serialize this symbol table. */ +); + +/* Deallocate encoder. */ +void +fsst_destroy(fsst_encoder_t*); + +/* Return a decoder structure from serialized format (typically used in a block-, file- or row-group header). */ +unsigned int /* OUT: number of bytes consumed in buf (0 on failure). */ +fsst_import( + fsst_decoder_t *decoder, /* IN: this symbol table will be overwritten. */ + unsigned char const *buf /* IN: pointer to a byte-buffer where fsst_export() serialized this symbol table. */ +); + +/* Return a decoder structure from an encoder. */ +fsst_decoder_t +fsst_decoder( + fsst_encoder_t *encoder +); + +/* Compress a batch of strings (on AVX512 machines best performance is obtained by compressing more than 32KB of string volume). */ +/* The output buffer must be large; at least "conservative space" (7+2*inputlength) for the first string for something to happen. */ +size_t /* OUT: the number of compressed strings (<=n) that fit the output buffer. */ +fsst_compress( + fsst_encoder_t *encoder, /* IN: encoder obtained from fsst_create(). */ + size_t nstrings, /* IN: number of strings in batch to compress. */ + const size_t lenIn[], /* IN: byte-lengths of the inputs */ + const unsigned char *strIn[], /* IN: input string start pointers. */ + size_t outsize, /* IN: byte-length of output buffer. */ + unsigned char *output, /* OUT: memory buffer to put the compressed strings in (one after the other). */ + size_t lenOut[], /* OUT: byte-lengths of the compressed strings. */ + unsigned char *strOut[] /* OUT: output string start pointers. Will all point into [output,output+size). */ +); + +/* Decompress a single string, inlined for speed. */ +inline size_t /* OUT: bytesize of the decompressed string. If > size, the decoded output is truncated to size. */ +fsst_decompress( + const fsst_decoder_t *decoder, /* IN: use this symbol table for compression. */ + size_t lenIn, /* IN: byte-length of compressed string. */ + const unsigned char *strIn, /* IN: compressed string. */ + size_t size, /* IN: byte-length of output buffer. */ + unsigned char *output /* OUT: memory buffer to put the decompressed string in. */ +) { + unsigned char*__restrict__ len = (unsigned char* __restrict__) decoder->len; + unsigned char*__restrict__ strOut = (unsigned char* __restrict__) output; + unsigned long long*__restrict__ symbol = (unsigned long long* __restrict__) decoder->symbol; + size_t code, posOut = 0, posIn = 0; +#ifndef FSST_MUST_ALIGN /* defining on platforms that require aligned memory access may help their performance */ +#define FSST_UNALIGNED_STORE(dst,src) memcpy((unsigned long long*) (dst), &(src), sizeof(unsigned long long)) +#if defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + while (posOut+32 <= size && posIn+4 <= lenIn) { + unsigned int nextBlock, escapeMask; + memcpy(&nextBlock, strIn+posIn, sizeof(unsigned int)); + escapeMask = (nextBlock&0x80808080u)&((((~nextBlock)&0x7F7F7F7Fu)+0x7F7F7F7Fu)^0x80808080u); + if (escapeMask == 0) { + code = strIn[posIn++]; FSST_UNALIGNED_STORE(strOut+posOut, symbol[code]); posOut += len[code]; + code = strIn[posIn++]; FSST_UNALIGNED_STORE(strOut+posOut, symbol[code]); posOut += len[code]; + code = strIn[posIn++]; FSST_UNALIGNED_STORE(strOut+posOut, symbol[code]); posOut += len[code]; + code = strIn[posIn++]; FSST_UNALIGNED_STORE(strOut+posOut, symbol[code]); posOut += len[code]; + } else { + unsigned long firstEscapePos=__builtin_ctzl((unsigned long long) escapeMask)>>3; + switch(firstEscapePos) { /* Duff's device */ + case 3: code = strIn[posIn++]; FSST_UNALIGNED_STORE(strOut+posOut, symbol[code]); posOut += len[code]; + // fall through + case 2: code = strIn[posIn++]; FSST_UNALIGNED_STORE(strOut+posOut, symbol[code]); posOut += len[code]; + // fall through + case 1: code = strIn[posIn++]; FSST_UNALIGNED_STORE(strOut+posOut, symbol[code]); posOut += len[code]; + // fall through + case 0: posIn+=2; strOut[posOut++] = strIn[posIn-1]; /* decompress an escaped byte */ + } + } + } + if (posOut+32 <= size) { // handle the possibly 3 last bytes without a loop + if (posIn+2 <= lenIn) { + strOut[posOut] = strIn[posIn+1]; + if (strIn[posIn] != FSST_ESC) { + code = strIn[posIn++]; FSST_UNALIGNED_STORE(strOut+posOut, symbol[code]); posOut += len[code]; + if (strIn[posIn] != FSST_ESC) { + code = strIn[posIn++]; FSST_UNALIGNED_STORE(strOut+posOut, symbol[code]); posOut += len[code]; + } else { + posIn += 2; strOut[posOut++] = strIn[posIn-1]; + } + } else { + posIn += 2; posOut++; + } + } + if (posIn < lenIn) { // last code cannot be an escape + code = strIn[posIn++]; FSST_UNALIGNED_STORE(strOut+posOut, symbol[code]); posOut += len[code]; + } + } +#else + while (posOut+8 <= size && posIn < lenIn) + if ((code = strIn[posIn++]) < FSST_ESC) { /* symbol compressed as code? */ + FSST_UNALIGNED_STORE(strOut+posOut, symbol[code]); /* unaligned memory write */ + posOut += len[code]; + } else { + strOut[posOut] = strIn[posIn]; /* decompress an escaped byte */ + posIn++; posOut++; + } +#endif +#endif + while (posIn < lenIn) + if ((code = strIn[posIn++]) < FSST_ESC) { + size_t posWrite = posOut, endWrite = posOut + len[code]; + unsigned char* __restrict__ symbolPointer = ((unsigned char* __restrict__) &symbol[code]) - posWrite; + if ((posOut = endWrite) > size) endWrite = size; + for(; posWrite < endWrite; posWrite++) /* only write if there is room */ + strOut[posWrite] = symbolPointer[posWrite]; + } else { + if (posOut < size) strOut[posOut] = strIn[posIn]; /* idem */ + posIn++; posOut++; + } + if (posOut >= size && (decoder->zeroTerminated&1)) strOut[size-1] = 0; + return posOut; /* full size of decompressed string (could be >size, then the actually decompressed part) */ +} + +#ifdef __cplusplus +} +#endif +#endif /* FSST_INCLUDED_H */ diff --git a/cpp/src/arrow/vendored/fsst/fsst_avx512.cpp b/cpp/src/arrow/vendored/fsst/fsst_avx512.cpp new file mode 100644 index 000000000000..e43d3e03652c --- /dev/null +++ b/cpp/src/arrow/vendored/fsst/fsst_avx512.cpp @@ -0,0 +1,149 @@ +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +#include "libfsst.hpp" + +#if defined(__x86_64__) || defined(_M_X64) +#include + +#ifdef _WIN32 +namespace libfsst { +bool fsst_hasAVX512() { + int info[4]; + __cpuidex(info, 0x00000007, 0); + return (info[1]>>16)&1; +} +} // namespace libfsst +#else +#include +namespace libfsst { +bool fsst_hasAVX512() { + int info[4]; + __cpuid_count(0x00000007, 0, info[0], info[1], info[2], info[3]); + return (info[1]>>16)&1; +} +} // namespace libfsst +#endif +#else +namespace libfsst { +bool fsst_hasAVX512() { return false; } +} // namespace libfsst +#endif + +namespace libfsst { + +// BULK COMPRESSION OF STRINGS +// +// In one call of this function, we can compress 512 strings, each of maximum length 511 bytes. +// strings can be shorter than 511 bytes, no problem, but if they are longer we need to cut them up. +// +// In each iteration of the while loop, we find one code in each of the unroll*8 strings, i.e. (8,16,24 or 32) for resp. unroll=1,2,3,4 +// unroll3 performs best on my hardware +// +// In the worst case, each final encoded string occupies 512KB bytes (512*1024; with 1024=512xexception, exception = 2 bytes). +// - hence codeBase is a buffer of 512KB (needs 19 bits jobs), symbolBase of 256KB (needs 18 bits jobs). +// +// 'jobX' controls the encoding of each string and is therefore a u64 with format [out:19][pos:9][end:18][cur:18] (low-to-high bits) +// The field 'pos' tells which string we are processing (0..511). We need this info as strings will complete compressing out-of-order. +// +// Strings will have different lengths, and when a string is finished, we reload from the buffer of 512 input strings. +// This continues until we have less than (8,16,24 or 32; depending on unroll) strings left to process. +// - so 'processed' is the amount of strings we started processing and it is between [480,512]. +// Note that when we quit, there will still be some (<32) strings that we started to process but which are unfinished. +// - so 'unfinished' is that amount. These unfinished strings will be encoded further using the scalar method. +// +// Apart from the coded strings, we return in a output[] array of size 'processed' the job values of the 'finished' strings. +// In the following 'unfinished' slots (processed=finished+unfinished) we output the 'job' values of the unfinished strings. +// +// For the finished strings, we need [out:19] to see the compressed size and [pos:9] to see which string we refer to. +// For the unfinished strings, we need all fields of 'job' to continue the compression with scalar code (see SIMD code in compressBatch). +// +// THIS IS A SEPARATE CODE FILE NOT BECAUSE OF MY LOVE FOR MODULARIZED CODE BUT BECAUSE IT ALLOWS TO COMPILE IT WITH DIFFERENT FLAGS +// in particular, unrolling is crucial for gather/scatter performance, but requires registers. the #define all_* expressions however, +// will be detected to be constants by g++ -O2 and will be precomputed and placed into AVX512 registers - spoiling 9 of them. +// This reduces the effectiveness of unrolling, hence -O2 makes the loop perform worse than -O1 which skips this optimization. +// Assembly inspection confirmed that 3-way unroll with -O1 avoids needless load/stores. + +size_t fsst_compressAVX512(SymbolTable &symbolTable, u8* codeBase, u8* symbolBase, SIMDjob *input, SIMDjob *output, size_t n, size_t unroll) { + size_t processed = 0; + // define some constants (all_x means that all 8 lanes contain 64-bits value X) +#ifdef __AVX512F__ + //__m512i all_suffixLim= _mm512_broadcastq_epi64(_mm_set1_epi64((__m64) (u64) symbolTable->suffixLim)); -- for variants b,c + __m512i all_MASK = _mm512_broadcastq_epi64(_mm_set1_epi64((__m64) (u64) -1)); + __m512i all_PRIME = _mm512_broadcastq_epi64(_mm_set1_epi64((__m64) (u64) FSST_HASH_PRIME)); + __m512i all_ICL_FREE = _mm512_broadcastq_epi64(_mm_set1_epi64((__m64) (u64) FSST_ICL_FREE)); +#define all_HASH _mm512_srli_epi64(all_MASK, 64-FSST_HASH_LOG2SIZE) +#define all_ONE _mm512_srli_epi64(all_MASK, 63) +#define all_M19 _mm512_srli_epi64(all_MASK, 45) +#define all_M18 _mm512_srli_epi64(all_MASK, 46) +#define all_M28 _mm512_srli_epi64(all_MASK, 36) +#define all_FFFFFF _mm512_srli_epi64(all_MASK, 40) +#define all_FFFF _mm512_srli_epi64(all_MASK, 48) +#define all_FF _mm512_srli_epi64(all_MASK, 56) + + SIMDjob *inputEnd = input+n; + assert(n >= unroll*8 && n <= 512); // should be close to 512 + __m512i job1, job2, job3, job4; // will contain current jobs, for each unroll 1,2,3,4 + __mmask8 loadmask1 = 255, loadmask2 = 255*(unroll>1), loadmask3 = 255*(unroll>2), loadmask4 = 255*(unroll>3); // 2b loaded new strings bitmask per unroll + u32 delta1 = 8, delta2 = 8*(unroll>1), delta3 = 8*(unroll>2), delta4 = 8*(unroll>3); // #new loads this SIMD iteration per unroll + + if (unroll >= 4) { + while (input+delta1+delta2+delta3+delta4 < inputEnd) { + #include "fsst_avx512_unroll4.inc" + } + } else if (unroll == 3) { + while (input+delta1+delta2+delta3 < inputEnd) { + #include "fsst_avx512_unroll3.inc" + } + } else if (unroll == 2) { + while (input+delta1+delta2 < inputEnd) { + #include "fsst_avx512_unroll2.inc" + } + } else { + while (input+delta1 < inputEnd) { + #include "fsst_avx512_unroll1.inc" + } + } + + // flush the job states of the unfinished strings at the end of output[] + processed = n - (inputEnd - input); + u32 unfinished = 0; + if (unroll > 1) { + if (unroll > 2) { + if (unroll > 3) { + _mm512_mask_compressstoreu_epi64(output+unfinished, loadmask4=~loadmask4, job4); + unfinished += _mm_popcnt_u32((int) loadmask4); + } + _mm512_mask_compressstoreu_epi64(output+unfinished, loadmask3=~loadmask3, job3); + unfinished += _mm_popcnt_u32((int) loadmask3); + } + _mm512_mask_compressstoreu_epi64(output+unfinished, loadmask2=~loadmask2, job2); + unfinished += _mm_popcnt_u32((int) loadmask2); + } + _mm512_mask_compressstoreu_epi64(output+unfinished, loadmask1=~loadmask1, job1); +#else + (void) symbolTable; + (void) codeBase; + (void) symbolBase; + (void) input; + (void) output; + (void) n; + (void) unroll; +#endif + return processed; +} +} // namespace libfsst diff --git a/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll1.inc b/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll1.inc new file mode 100644 index 000000000000..cc2581fbe183 --- /dev/null +++ b/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll1.inc @@ -0,0 +1,57 @@ +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E1PRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +// + // load new jobs in the empty lanes (initially, all lanes are empty, so loadmask1=11111111, delta1=8). + job1 = _mm512_mask_expandloadu_epi64(job1, loadmask1, input); input += delta1; + // load the next 8 input string bytes (uncompressed data, aka 'symbols'). + __m512i word1 = _mm512_i64gather_epi64(_mm512_srli_epi64(job1, 46), symbolBase, 1); + // load 16-bits codes from the 2-byte-prefix keyed lookup table. It also store 1-byte codes in all free slots. + // code1: Lowest 8 bits contain the code. Eleventh bit is whether it is an escaped code. Next 4 bits is length (2 or 1). + __m512i code1 = _mm512_i64gather_epi64(_mm512_and_epi64(word1, all_FFFF), symbolTable.shortCodes, sizeof(u16)); + // get the first three bytes of the string. + __m512i pos1 = _mm512_mullo_epi64(_mm512_and_epi64(word1, all_FFFFFF), all_PRIME); + // hash them into a random number: pos1 = pos1*PRIME; pos1 ^= pos1>>SHIFT + pos1 = _mm512_slli_epi64(_mm512_and_epi64(_mm512_xor_epi64(pos1,_mm512_srli_epi64(pos1,FSST_SHIFT)), all_HASH), 4); + // lookup in the 3-byte-prefix keyed hash table + __m512i icl1 = _mm512_i64gather_epi64(pos1, (((char*) symbolTable.hashTab) + 8), 1); + // speculatively store the first input byte into the second position of the write1 register (in case it turns out to be an escaped byte). + __m512i write1 = _mm512_slli_epi64(_mm512_and_epi64(word1, all_FF), 8); + // lookup just like the icl1 above, but loads the next 8 bytes. This fetches the actual string bytes in the hash table. + __m512i symb1 = _mm512_i64gather_epi64(pos1, (((char*) symbolTable.hashTab) + 0), 1); + // generate the FF..FF mask with an FF for each byte of the symbol (we need to AND the input with this to correctly check equality). + pos1 = _mm512_srlv_epi64(all_MASK, _mm512_and_epi64(icl1, all_FF)); + // check symbol < |str| as well as whether it is an occupied slot (cmplt checks both conditions at once) and check string equality (cmpeq). + __mmask8 match1 = _mm512_cmpeq_epi64_mask(symb1, _mm512_and_epi64(word1, pos1)) & _mm512_cmplt_epi64_mask(icl1, all_ICL_FREE); + // for the hits, overwrite the codes with what comes from the hash table (codes for symbols of length >=3). The rest stays with what shortCodes gave. + code1 = _mm512_mask_mov_epi64(code1, match1, _mm512_srli_epi64(icl1, 16)); + // write out the code byte as the first output byte. Notice that this byte may also be the escape code 255 (for escapes) coming from shortCodes. + write1 = _mm512_or_epi64(write1, _mm512_and_epi64(code1, all_FF)); + // zip the irrelevant 6 bytes (just stay with the 2 relevant bytes containing the 16-bits code) + code1 = _mm512_and_epi64(code1, all_FFFF); + // write out the compressed data. It writes 8 bytes, but only 1 byte is relevant :-(or 2 bytes are, in case of an escape code) + _mm512_i64scatter_epi64(codeBase, _mm512_and_epi64(job1, all_M19), write1, 1); + // increase the job1.cur field in the job with the symbol length (for this, shift away 12 bits from the code) + job1 = _mm512_add_epi64(job1, _mm512_slli_epi64(_mm512_srli_epi64(code1, FSST_LEN_BITS), 46)); + // increase the job1.out' field with one, or two in case of an escape code (add 1 plus the escape bit, i.e the 8th) + job1 = _mm512_add_epi64(job1, _mm512_add_epi64(all_ONE, _mm512_and_epi64(_mm512_srli_epi64(code1, 8), all_ONE))); + // test which lanes are done now (job1.cur==job1.end), cur starts at bit 46, end starts at bit 28 (the highest 2x18 bits in the job1 register) + loadmask1 = _mm512_cmpeq_epi64_mask(_mm512_srli_epi64(job1, 46), _mm512_and_epi64(_mm512_srli_epi64(job1, 28), all_M18)); + // calculate the amount of lanes in job1 that are done + delta1 = _mm_popcnt_u32((int) loadmask1); + // write out the job state for the lanes that are done (we need the final 'job1.out' value to compute the compressed string length) + _mm512_mask_compressstoreu_epi64(output, loadmask1, job1); output += delta1; diff --git a/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll2.inc b/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll2.inc new file mode 100644 index 000000000000..1dd5b1b452c5 --- /dev/null +++ b/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll2.inc @@ -0,0 +1,114 @@ +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// +// +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// furnished to do so, subject to the following conditions: +// +// +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E1PRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E2PRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +// +// + // load new jobs in the empty lanes (initially, all lanes are empty, so loadmask1=11111111, delta1=8). + // load new jobs in the empty lanes (initially, all lanes are empty, so loadmask2=11111111, delta2=8). + job1 = _mm512_mask_expandloadu_epi64(job1, loadmask1, input); input += delta1; + job2 = _mm512_mask_expandloadu_epi64(job2, loadmask2, input); input += delta2; + // load the next 8 input string bytes (uncompressed data, aka 'symbols'). + // load the next 8 input string bytes (uncompressed data, aka 'symbols'). + __m512i word1 = _mm512_i64gather_epi64(_mm512_srli_epi64(job1, 46), symbolBase, 1); + __m512i word2 = _mm512_i64gather_epi64(_mm512_srli_epi64(job2, 46), symbolBase, 1); + // load 16-bits codes from the 2-byte-prefix keyed lookup table. It also store 1-byte codes in all free slots. + // load 16-bits codes from the 2-byte-prefix keyed lookup table. It also store 1-byte codes in all free slots. + // code1: Lowest 8 bits contain the code. Eleventh bit is whether it is an escaped code. Next 4 bits is length (2 or 1). + // code2: Lowest 8 bits contain the code. Eleventh bit is whether it is an escaped code. Next 4 bits is length (2 or 1). + __m512i code1 = _mm512_i64gather_epi64(_mm512_and_epi64(word1, all_FFFF), symbolTable.shortCodes, sizeof(u16)); + __m512i code2 = _mm512_i64gather_epi64(_mm512_and_epi64(word2, all_FFFF), symbolTable.shortCodes, sizeof(u16)); + // get the first three bytes of the string. + // get the first three bytes of the string. + __m512i pos1 = _mm512_mullo_epi64(_mm512_and_epi64(word1, all_FFFFFF), all_PRIME); + __m512i pos2 = _mm512_mullo_epi64(_mm512_and_epi64(word2, all_FFFFFF), all_PRIME); + // hash them into a random number: pos1 = pos1*PRIME; pos1 ^= pos1>>SHIFT + // hash them into a random number: pos2 = pos2*PRIME; pos2 ^= pos2>>SHIFT + pos1 = _mm512_slli_epi64(_mm512_and_epi64(_mm512_xor_epi64(pos1,_mm512_srli_epi64(pos1,FSST_SHIFT)), all_HASH), 4); + pos2 = _mm512_slli_epi64(_mm512_and_epi64(_mm512_xor_epi64(pos2,_mm512_srli_epi64(pos2,FSST_SHIFT)), all_HASH), 4); + // lookup in the 3-byte-prefix keyed hash table + // lookup in the 3-byte-prefix keyed hash table + __m512i icl1 = _mm512_i64gather_epi64(pos1, (((char*) symbolTable.hashTab) + 8), 1); + __m512i icl2 = _mm512_i64gather_epi64(pos2, (((char*) symbolTable.hashTab) + 8), 1); + // speculatively store the first input byte into the second position of the write1 register (in case it turns out to be an escaped byte). + // speculatively store the first input byte into the second position of the write2 register (in case it turns out to be an escaped byte). + __m512i write1 = _mm512_slli_epi64(_mm512_and_epi64(word1, all_FF), 8); + __m512i write2 = _mm512_slli_epi64(_mm512_and_epi64(word2, all_FF), 8); + // lookup just like the icl1 above, but loads the next 8 bytes. This fetches the actual string bytes in the hash table. + // lookup just like the icl2 above, but loads the next 8 bytes. This fetches the actual string bytes in the hash table. + __m512i symb1 = _mm512_i64gather_epi64(pos1, (((char*) symbolTable.hashTab) + 0), 1); + __m512i symb2 = _mm512_i64gather_epi64(pos2, (((char*) symbolTable.hashTab) + 0), 1); + // generate the FF..FF mask with an FF for each byte of the symbol (we need to AND the input with this to correctly check equality). + // generate the FF..FF mask with an FF for each byte of the symbol (we need to AND the input with this to correctly check equality). + pos1 = _mm512_srlv_epi64(all_MASK, _mm512_and_epi64(icl1, all_FF)); + pos2 = _mm512_srlv_epi64(all_MASK, _mm512_and_epi64(icl2, all_FF)); + // check symbol < |str| as well as whether it is an occupied slot (cmplt checks both conditions at once) and check string equality (cmpeq). + // check symbol < |str| as well as whether it is an occupied slot (cmplt checks both conditions at once) and check string equality (cmpeq). + __mmask8 match1 = _mm512_cmpeq_epi64_mask(symb1, _mm512_and_epi64(word1, pos1)) & _mm512_cmplt_epi64_mask(icl1, all_ICL_FREE); + __mmask8 match2 = _mm512_cmpeq_epi64_mask(symb2, _mm512_and_epi64(word2, pos2)) & _mm512_cmplt_epi64_mask(icl2, all_ICL_FREE); + // for the hits, overwrite the codes with what comes from the hash table (codes for symbols of length >=3). The rest stays with what shortCodes gave. + // for the hits, overwrite the codes with what comes from the hash table (codes for symbols of length >=3). The rest stays with what shortCodes gave. + code1 = _mm512_mask_mov_epi64(code1, match1, _mm512_srli_epi64(icl1, 16)); + code2 = _mm512_mask_mov_epi64(code2, match2, _mm512_srli_epi64(icl2, 16)); + // write out the code byte as the first output byte. Notice that this byte may also be the escape code 255 (for escapes) coming from shortCodes. + // write out the code byte as the first output byte. Notice that this byte may also be the escape code 255 (for escapes) coming from shortCodes. + write1 = _mm512_or_epi64(write1, _mm512_and_epi64(code1, all_FF)); + write2 = _mm512_or_epi64(write2, _mm512_and_epi64(code2, all_FF)); + // zip the irrelevant 6 bytes (just stay with the 2 relevant bytes containing the 16-bits code) + // zip the irrelevant 6 bytes (just stay with the 2 relevant bytes containing the 16-bits code) + code1 = _mm512_and_epi64(code1, all_FFFF); + code2 = _mm512_and_epi64(code2, all_FFFF); + // write out the compressed data. It writes 8 bytes, but only 1 byte is relevant :-(or 2 bytes are, in case of an escape code) + // write out the compressed data. It writes 8 bytes, but only 1 byte is relevant :-(or 2 bytes are, in case of an escape code) + _mm512_i64scatter_epi64(codeBase, _mm512_and_epi64(job1, all_M19), write1, 1); + _mm512_i64scatter_epi64(codeBase, _mm512_and_epi64(job2, all_M19), write2, 1); + // increase the job1.cur field in the job with the symbol length (for this, shift away 12 bits from the code) + // increase the job2.cur field in the job with the symbol length (for this, shift away 12 bits from the code) + job1 = _mm512_add_epi64(job1, _mm512_slli_epi64(_mm512_srli_epi64(code1, FSST_LEN_BITS), 46)); + job2 = _mm512_add_epi64(job2, _mm512_slli_epi64(_mm512_srli_epi64(code2, FSST_LEN_BITS), 46)); + // increase the job1.out' field with one, or two in case of an escape code (add 1 plus the escape bit, i.e the 8th) + // increase the job2.out' field with one, or two in case of an escape code (add 1 plus the escape bit, i.e the 8th) + job1 = _mm512_add_epi64(job1, _mm512_add_epi64(all_ONE, _mm512_and_epi64(_mm512_srli_epi64(code1, 8), all_ONE))); + job2 = _mm512_add_epi64(job2, _mm512_add_epi64(all_ONE, _mm512_and_epi64(_mm512_srli_epi64(code2, 8), all_ONE))); + // test which lanes are done now (job1.cur==job1.end), cur starts at bit 46, end starts at bit 28 (the highest 2x18 bits in the job1 register) + // test which lanes are done now (job2.cur==job2.end), cur starts at bit 46, end starts at bit 28 (the highest 2x18 bits in the job2 register) + loadmask1 = _mm512_cmpeq_epi64_mask(_mm512_srli_epi64(job1, 46), _mm512_and_epi64(_mm512_srli_epi64(job1, 28), all_M18)); + loadmask2 = _mm512_cmpeq_epi64_mask(_mm512_srli_epi64(job2, 46), _mm512_and_epi64(_mm512_srli_epi64(job2, 28), all_M18)); + // calculate the amount of lanes in job1 that are done + // calculate the amount of lanes in job2 that are done + delta1 = _mm_popcnt_u32((int) loadmask1); + delta2 = _mm_popcnt_u32((int) loadmask2); + // write out the job state for the lanes that are done (we need the final 'job1.out' value to compute the compressed string length) + // write out the job state for the lanes that are done (we need the final 'job2.out' value to compute the compressed string length) + _mm512_mask_compressstoreu_epi64(output, loadmask1, job1); output += delta1; + _mm512_mask_compressstoreu_epi64(output, loadmask2, job2); output += delta2; diff --git a/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll3.inc b/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll3.inc new file mode 100644 index 000000000000..2e4af50b8fd9 --- /dev/null +++ b/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll3.inc @@ -0,0 +1,171 @@ +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// +// +// +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// furnished to do so, subject to the following conditions: +// furnished to do so, subject to the following conditions: +// +// +// +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E1PRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E2PRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E3PRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// +// +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +// +// +// + // load new jobs in the empty lanes (initially, all lanes are empty, so loadmask1=11111111, delta1=8). + // load new jobs in the empty lanes (initially, all lanes are empty, so loadmask2=11111111, delta2=8). + // load new jobs in the empty lanes (initially, all lanes are empty, so loadmask3=11111111, delta3=8). + job1 = _mm512_mask_expandloadu_epi64(job1, loadmask1, input); input += delta1; + job2 = _mm512_mask_expandloadu_epi64(job2, loadmask2, input); input += delta2; + job3 = _mm512_mask_expandloadu_epi64(job3, loadmask3, input); input += delta3; + // load the next 8 input string bytes (uncompressed data, aka 'symbols'). + // load the next 8 input string bytes (uncompressed data, aka 'symbols'). + // load the next 8 input string bytes (uncompressed data, aka 'symbols'). + __m512i word1 = _mm512_i64gather_epi64(_mm512_srli_epi64(job1, 46), symbolBase, 1); + __m512i word2 = _mm512_i64gather_epi64(_mm512_srli_epi64(job2, 46), symbolBase, 1); + __m512i word3 = _mm512_i64gather_epi64(_mm512_srli_epi64(job3, 46), symbolBase, 1); + // load 16-bits codes from the 2-byte-prefix keyed lookup table. It also store 1-byte codes in all free slots. + // load 16-bits codes from the 2-byte-prefix keyed lookup table. It also store 1-byte codes in all free slots. + // load 16-bits codes from the 2-byte-prefix keyed lookup table. It also store 1-byte codes in all free slots. + // code1: Lowest 8 bits contain the code. Eleventh bit is whether it is an escaped code. Next 4 bits is length (2 or 1). + // code2: Lowest 8 bits contain the code. Eleventh bit is whether it is an escaped code. Next 4 bits is length (2 or 1). + // code3: Lowest 8 bits contain the code. Eleventh bit is whether it is an escaped code. Next 4 bits is length (2 or 1). + __m512i code1 = _mm512_i64gather_epi64(_mm512_and_epi64(word1, all_FFFF), symbolTable.shortCodes, sizeof(u16)); + __m512i code2 = _mm512_i64gather_epi64(_mm512_and_epi64(word2, all_FFFF), symbolTable.shortCodes, sizeof(u16)); + __m512i code3 = _mm512_i64gather_epi64(_mm512_and_epi64(word3, all_FFFF), symbolTable.shortCodes, sizeof(u16)); + // get the first three bytes of the string. + // get the first three bytes of the string. + // get the first three bytes of the string. + __m512i pos1 = _mm512_mullo_epi64(_mm512_and_epi64(word1, all_FFFFFF), all_PRIME); + __m512i pos2 = _mm512_mullo_epi64(_mm512_and_epi64(word2, all_FFFFFF), all_PRIME); + __m512i pos3 = _mm512_mullo_epi64(_mm512_and_epi64(word3, all_FFFFFF), all_PRIME); + // hash them into a random number: pos1 = pos1*PRIME; pos1 ^= pos1>>SHIFT + // hash them into a random number: pos2 = pos2*PRIME; pos2 ^= pos2>>SHIFT + // hash them into a random number: pos3 = pos3*PRIME; pos3 ^= pos3>>SHIFT + pos1 = _mm512_slli_epi64(_mm512_and_epi64(_mm512_xor_epi64(pos1,_mm512_srli_epi64(pos1,FSST_SHIFT)), all_HASH), 4); + pos2 = _mm512_slli_epi64(_mm512_and_epi64(_mm512_xor_epi64(pos2,_mm512_srli_epi64(pos2,FSST_SHIFT)), all_HASH), 4); + pos3 = _mm512_slli_epi64(_mm512_and_epi64(_mm512_xor_epi64(pos3,_mm512_srli_epi64(pos3,FSST_SHIFT)), all_HASH), 4); + // lookup in the 3-byte-prefix keyed hash table + // lookup in the 3-byte-prefix keyed hash table + // lookup in the 3-byte-prefix keyed hash table + __m512i icl1 = _mm512_i64gather_epi64(pos1, (((char*) symbolTable.hashTab) + 8), 1); + __m512i icl2 = _mm512_i64gather_epi64(pos2, (((char*) symbolTable.hashTab) + 8), 1); + __m512i icl3 = _mm512_i64gather_epi64(pos3, (((char*) symbolTable.hashTab) + 8), 1); + // speculatively store the first input byte into the second position of the write1 register (in case it turns out to be an escaped byte). + // speculatively store the first input byte into the second position of the write2 register (in case it turns out to be an escaped byte). + // speculatively store the first input byte into the second position of the write3 register (in case it turns out to be an escaped byte). + __m512i write1 = _mm512_slli_epi64(_mm512_and_epi64(word1, all_FF), 8); + __m512i write2 = _mm512_slli_epi64(_mm512_and_epi64(word2, all_FF), 8); + __m512i write3 = _mm512_slli_epi64(_mm512_and_epi64(word3, all_FF), 8); + // lookup just like the icl1 above, but loads the next 8 bytes. This fetches the actual string bytes in the hash table. + // lookup just like the icl2 above, but loads the next 8 bytes. This fetches the actual string bytes in the hash table. + // lookup just like the icl3 above, but loads the next 8 bytes. This fetches the actual string bytes in the hash table. + __m512i symb1 = _mm512_i64gather_epi64(pos1, (((char*) symbolTable.hashTab) + 0), 1); + __m512i symb2 = _mm512_i64gather_epi64(pos2, (((char*) symbolTable.hashTab) + 0), 1); + __m512i symb3 = _mm512_i64gather_epi64(pos3, (((char*) symbolTable.hashTab) + 0), 1); + // generate the FF..FF mask with an FF for each byte of the symbol (we need to AND the input with this to correctly check equality). + // generate the FF..FF mask with an FF for each byte of the symbol (we need to AND the input with this to correctly check equality). + // generate the FF..FF mask with an FF for each byte of the symbol (we need to AND the input with this to correctly check equality). + pos1 = _mm512_srlv_epi64(all_MASK, _mm512_and_epi64(icl1, all_FF)); + pos2 = _mm512_srlv_epi64(all_MASK, _mm512_and_epi64(icl2, all_FF)); + pos3 = _mm512_srlv_epi64(all_MASK, _mm512_and_epi64(icl3, all_FF)); + // check symbol < |str| as well as whether it is an occupied slot (cmplt checks both conditions at once) and check string equality (cmpeq). + // check symbol < |str| as well as whether it is an occupied slot (cmplt checks both conditions at once) and check string equality (cmpeq). + // check symbol < |str| as well as whether it is an occupied slot (cmplt checks both conditions at once) and check string equality (cmpeq). + __mmask8 match1 = _mm512_cmpeq_epi64_mask(symb1, _mm512_and_epi64(word1, pos1)) & _mm512_cmplt_epi64_mask(icl1, all_ICL_FREE); + __mmask8 match2 = _mm512_cmpeq_epi64_mask(symb2, _mm512_and_epi64(word2, pos2)) & _mm512_cmplt_epi64_mask(icl2, all_ICL_FREE); + __mmask8 match3 = _mm512_cmpeq_epi64_mask(symb3, _mm512_and_epi64(word3, pos3)) & _mm512_cmplt_epi64_mask(icl3, all_ICL_FREE); + // for the hits, overwrite the codes with what comes from the hash table (codes for symbols of length >=3). The rest stays with what shortCodes gave. + // for the hits, overwrite the codes with what comes from the hash table (codes for symbols of length >=3). The rest stays with what shortCodes gave. + // for the hits, overwrite the codes with what comes from the hash table (codes for symbols of length >=3). The rest stays with what shortCodes gave. + code1 = _mm512_mask_mov_epi64(code1, match1, _mm512_srli_epi64(icl1, 16)); + code2 = _mm512_mask_mov_epi64(code2, match2, _mm512_srli_epi64(icl2, 16)); + code3 = _mm512_mask_mov_epi64(code3, match3, _mm512_srli_epi64(icl3, 16)); + // write out the code byte as the first output byte. Notice that this byte may also be the escape code 255 (for escapes) coming from shortCodes. + // write out the code byte as the first output byte. Notice that this byte may also be the escape code 255 (for escapes) coming from shortCodes. + // write out the code byte as the first output byte. Notice that this byte may also be the escape code 255 (for escapes) coming from shortCodes. + write1 = _mm512_or_epi64(write1, _mm512_and_epi64(code1, all_FF)); + write2 = _mm512_or_epi64(write2, _mm512_and_epi64(code2, all_FF)); + write3 = _mm512_or_epi64(write3, _mm512_and_epi64(code3, all_FF)); + // zip the irrelevant 6 bytes (just stay with the 2 relevant bytes containing the 16-bits code) + // zip the irrelevant 6 bytes (just stay with the 2 relevant bytes containing the 16-bits code) + // zip the irrelevant 6 bytes (just stay with the 2 relevant bytes containing the 16-bits code) + code1 = _mm512_and_epi64(code1, all_FFFF); + code2 = _mm512_and_epi64(code2, all_FFFF); + code3 = _mm512_and_epi64(code3, all_FFFF); + // write out the compressed data. It writes 8 bytes, but only 1 byte is relevant :-(or 2 bytes are, in case of an escape code) + // write out the compressed data. It writes 8 bytes, but only 1 byte is relevant :-(or 2 bytes are, in case of an escape code) + // write out the compressed data. It writes 8 bytes, but only 1 byte is relevant :-(or 2 bytes are, in case of an escape code) + _mm512_i64scatter_epi64(codeBase, _mm512_and_epi64(job1, all_M19), write1, 1); + _mm512_i64scatter_epi64(codeBase, _mm512_and_epi64(job2, all_M19), write2, 1); + _mm512_i64scatter_epi64(codeBase, _mm512_and_epi64(job3, all_M19), write3, 1); + // increase the job1.cur field in the job with the symbol length (for this, shift away 12 bits from the code) + // increase the job2.cur field in the job with the symbol length (for this, shift away 12 bits from the code) + // increase the job3.cur field in the job with the symbol length (for this, shift away 12 bits from the code) + job1 = _mm512_add_epi64(job1, _mm512_slli_epi64(_mm512_srli_epi64(code1, FSST_LEN_BITS), 46)); + job2 = _mm512_add_epi64(job2, _mm512_slli_epi64(_mm512_srli_epi64(code2, FSST_LEN_BITS), 46)); + job3 = _mm512_add_epi64(job3, _mm512_slli_epi64(_mm512_srli_epi64(code3, FSST_LEN_BITS), 46)); + // increase the job1.out' field with one, or two in case of an escape code (add 1 plus the escape bit, i.e the 8th) + // increase the job2.out' field with one, or two in case of an escape code (add 1 plus the escape bit, i.e the 8th) + // increase the job3.out' field with one, or two in case of an escape code (add 1 plus the escape bit, i.e the 8th) + job1 = _mm512_add_epi64(job1, _mm512_add_epi64(all_ONE, _mm512_and_epi64(_mm512_srli_epi64(code1, 8), all_ONE))); + job2 = _mm512_add_epi64(job2, _mm512_add_epi64(all_ONE, _mm512_and_epi64(_mm512_srli_epi64(code2, 8), all_ONE))); + job3 = _mm512_add_epi64(job3, _mm512_add_epi64(all_ONE, _mm512_and_epi64(_mm512_srli_epi64(code3, 8), all_ONE))); + // test which lanes are done now (job1.cur==job1.end), cur starts at bit 46, end starts at bit 28 (the highest 2x18 bits in the job1 register) + // test which lanes are done now (job2.cur==job2.end), cur starts at bit 46, end starts at bit 28 (the highest 2x18 bits in the job2 register) + // test which lanes are done now (job3.cur==job3.end), cur starts at bit 46, end starts at bit 28 (the highest 2x18 bits in the job3 register) + loadmask1 = _mm512_cmpeq_epi64_mask(_mm512_srli_epi64(job1, 46), _mm512_and_epi64(_mm512_srli_epi64(job1, 28), all_M18)); + loadmask2 = _mm512_cmpeq_epi64_mask(_mm512_srli_epi64(job2, 46), _mm512_and_epi64(_mm512_srli_epi64(job2, 28), all_M18)); + loadmask3 = _mm512_cmpeq_epi64_mask(_mm512_srli_epi64(job3, 46), _mm512_and_epi64(_mm512_srli_epi64(job3, 28), all_M18)); + // calculate the amount of lanes in job1 that are done + // calculate the amount of lanes in job2 that are done + // calculate the amount of lanes in job3 that are done + delta1 = _mm_popcnt_u32((int) loadmask1); + delta2 = _mm_popcnt_u32((int) loadmask2); + delta3 = _mm_popcnt_u32((int) loadmask3); + // write out the job state for the lanes that are done (we need the final 'job1.out' value to compute the compressed string length) + // write out the job state for the lanes that are done (we need the final 'job2.out' value to compute the compressed string length) + // write out the job state for the lanes that are done (we need the final 'job3.out' value to compute the compressed string length) + _mm512_mask_compressstoreu_epi64(output, loadmask1, job1); output += delta1; + _mm512_mask_compressstoreu_epi64(output, loadmask2, job2); output += delta2; + _mm512_mask_compressstoreu_epi64(output, loadmask3, job3); output += delta3; diff --git a/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll4.inc b/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll4.inc new file mode 100644 index 000000000000..f211d1df9779 --- /dev/null +++ b/cpp/src/arrow/vendored/fsst/fsst_avx512_unroll4.inc @@ -0,0 +1,228 @@ +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// +// +// +// +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// +// +// +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// furnished to do so, subject to the following conditions: +// furnished to do so, subject to the following conditions: +// furnished to do so, subject to the following conditions: +// +// +// +// +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// +// +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E1PRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E2PRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E3PRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, E4PRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// +// +// +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +// +// +// +// + // load new jobs in the empty lanes (initially, all lanes are empty, so loadmask1=11111111, delta1=8). + // load new jobs in the empty lanes (initially, all lanes are empty, so loadmask2=11111111, delta2=8). + // load new jobs in the empty lanes (initially, all lanes are empty, so loadmask3=11111111, delta3=8). + // load new jobs in the empty lanes (initially, all lanes are empty, so loadmask4=11111111, delta4=8). + job1 = _mm512_mask_expandloadu_epi64(job1, loadmask1, input); input += delta1; + job2 = _mm512_mask_expandloadu_epi64(job2, loadmask2, input); input += delta2; + job3 = _mm512_mask_expandloadu_epi64(job3, loadmask3, input); input += delta3; + job4 = _mm512_mask_expandloadu_epi64(job4, loadmask4, input); input += delta4; + // load the next 8 input string bytes (uncompressed data, aka 'symbols'). + // load the next 8 input string bytes (uncompressed data, aka 'symbols'). + // load the next 8 input string bytes (uncompressed data, aka 'symbols'). + // load the next 8 input string bytes (uncompressed data, aka 'symbols'). + __m512i word1 = _mm512_i64gather_epi64(_mm512_srli_epi64(job1, 46), symbolBase, 1); + __m512i word2 = _mm512_i64gather_epi64(_mm512_srli_epi64(job2, 46), symbolBase, 1); + __m512i word3 = _mm512_i64gather_epi64(_mm512_srli_epi64(job3, 46), symbolBase, 1); + __m512i word4 = _mm512_i64gather_epi64(_mm512_srli_epi64(job4, 46), symbolBase, 1); + // load 16-bits codes from the 2-byte-prefix keyed lookup table. It also store 1-byte codes in all free slots. + // load 16-bits codes from the 2-byte-prefix keyed lookup table. It also store 1-byte codes in all free slots. + // load 16-bits codes from the 2-byte-prefix keyed lookup table. It also store 1-byte codes in all free slots. + // load 16-bits codes from the 2-byte-prefix keyed lookup table. It also store 1-byte codes in all free slots. + // code1: Lowest 8 bits contain the code. Eleventh bit is whether it is an escaped code. Next 4 bits is length (2 or 1). + // code2: Lowest 8 bits contain the code. Eleventh bit is whether it is an escaped code. Next 4 bits is length (2 or 1). + // code3: Lowest 8 bits contain the code. Eleventh bit is whether it is an escaped code. Next 4 bits is length (2 or 1). + // code4: Lowest 8 bits contain the code. Eleventh bit is whether it is an escaped code. Next 4 bits is length (2 or 1). + __m512i code1 = _mm512_i64gather_epi64(_mm512_and_epi64(word1, all_FFFF), symbolTable.shortCodes, sizeof(u16)); + __m512i code2 = _mm512_i64gather_epi64(_mm512_and_epi64(word2, all_FFFF), symbolTable.shortCodes, sizeof(u16)); + __m512i code3 = _mm512_i64gather_epi64(_mm512_and_epi64(word3, all_FFFF), symbolTable.shortCodes, sizeof(u16)); + __m512i code4 = _mm512_i64gather_epi64(_mm512_and_epi64(word4, all_FFFF), symbolTable.shortCodes, sizeof(u16)); + // get the first three bytes of the string. + // get the first three bytes of the string. + // get the first three bytes of the string. + // get the first three bytes of the string. + __m512i pos1 = _mm512_mullo_epi64(_mm512_and_epi64(word1, all_FFFFFF), all_PRIME); + __m512i pos2 = _mm512_mullo_epi64(_mm512_and_epi64(word2, all_FFFFFF), all_PRIME); + __m512i pos3 = _mm512_mullo_epi64(_mm512_and_epi64(word3, all_FFFFFF), all_PRIME); + __m512i pos4 = _mm512_mullo_epi64(_mm512_and_epi64(word4, all_FFFFFF), all_PRIME); + // hash them into a random number: pos1 = pos1*PRIME; pos1 ^= pos1>>SHIFT + // hash them into a random number: pos2 = pos2*PRIME; pos2 ^= pos2>>SHIFT + // hash them into a random number: pos3 = pos3*PRIME; pos3 ^= pos3>>SHIFT + // hash them into a random number: pos4 = pos4*PRIME; pos4 ^= pos4>>SHIFT + pos1 = _mm512_slli_epi64(_mm512_and_epi64(_mm512_xor_epi64(pos1,_mm512_srli_epi64(pos1,FSST_SHIFT)), all_HASH), 4); + pos2 = _mm512_slli_epi64(_mm512_and_epi64(_mm512_xor_epi64(pos2,_mm512_srli_epi64(pos2,FSST_SHIFT)), all_HASH), 4); + pos3 = _mm512_slli_epi64(_mm512_and_epi64(_mm512_xor_epi64(pos3,_mm512_srli_epi64(pos3,FSST_SHIFT)), all_HASH), 4); + pos4 = _mm512_slli_epi64(_mm512_and_epi64(_mm512_xor_epi64(pos4,_mm512_srli_epi64(pos4,FSST_SHIFT)), all_HASH), 4); + // lookup in the 3-byte-prefix keyed hash table + // lookup in the 3-byte-prefix keyed hash table + // lookup in the 3-byte-prefix keyed hash table + // lookup in the 3-byte-prefix keyed hash table + __m512i icl1 = _mm512_i64gather_epi64(pos1, (((char*) symbolTable.hashTab) + 8), 1); + __m512i icl2 = _mm512_i64gather_epi64(pos2, (((char*) symbolTable.hashTab) + 8), 1); + __m512i icl3 = _mm512_i64gather_epi64(pos3, (((char*) symbolTable.hashTab) + 8), 1); + __m512i icl4 = _mm512_i64gather_epi64(pos4, (((char*) symbolTable.hashTab) + 8), 1); + // speculatively store the first input byte into the second position of the write1 register (in case it turns out to be an escaped byte). + // speculatively store the first input byte into the second position of the write2 register (in case it turns out to be an escaped byte). + // speculatively store the first input byte into the second position of the write3 register (in case it turns out to be an escaped byte). + // speculatively store the first input byte into the second position of the write4 register (in case it turns out to be an escaped byte). + __m512i write1 = _mm512_slli_epi64(_mm512_and_epi64(word1, all_FF), 8); + __m512i write2 = _mm512_slli_epi64(_mm512_and_epi64(word2, all_FF), 8); + __m512i write3 = _mm512_slli_epi64(_mm512_and_epi64(word3, all_FF), 8); + __m512i write4 = _mm512_slli_epi64(_mm512_and_epi64(word4, all_FF), 8); + // lookup just like the icl1 above, but loads the next 8 bytes. This fetches the actual string bytes in the hash table. + // lookup just like the icl2 above, but loads the next 8 bytes. This fetches the actual string bytes in the hash table. + // lookup just like the icl3 above, but loads the next 8 bytes. This fetches the actual string bytes in the hash table. + // lookup just like the icl4 above, but loads the next 8 bytes. This fetches the actual string bytes in the hash table. + __m512i symb1 = _mm512_i64gather_epi64(pos1, (((char*) symbolTable.hashTab) + 0), 1); + __m512i symb2 = _mm512_i64gather_epi64(pos2, (((char*) symbolTable.hashTab) + 0), 1); + __m512i symb3 = _mm512_i64gather_epi64(pos3, (((char*) symbolTable.hashTab) + 0), 1); + __m512i symb4 = _mm512_i64gather_epi64(pos4, (((char*) symbolTable.hashTab) + 0), 1); + // generate the FF..FF mask with an FF for each byte of the symbol (we need to AND the input with this to correctly check equality). + // generate the FF..FF mask with an FF for each byte of the symbol (we need to AND the input with this to correctly check equality). + // generate the FF..FF mask with an FF for each byte of the symbol (we need to AND the input with this to correctly check equality). + // generate the FF..FF mask with an FF for each byte of the symbol (we need to AND the input with this to correctly check equality). + pos1 = _mm512_srlv_epi64(all_MASK, _mm512_and_epi64(icl1, all_FF)); + pos2 = _mm512_srlv_epi64(all_MASK, _mm512_and_epi64(icl2, all_FF)); + pos3 = _mm512_srlv_epi64(all_MASK, _mm512_and_epi64(icl3, all_FF)); + pos4 = _mm512_srlv_epi64(all_MASK, _mm512_and_epi64(icl4, all_FF)); + // check symbol < |str| as well as whether it is an occupied slot (cmplt checks both conditions at once) and check string equality (cmpeq). + // check symbol < |str| as well as whether it is an occupied slot (cmplt checks both conditions at once) and check string equality (cmpeq). + // check symbol < |str| as well as whether it is an occupied slot (cmplt checks both conditions at once) and check string equality (cmpeq). + // check symbol < |str| as well as whether it is an occupied slot (cmplt checks both conditions at once) and check string equality (cmpeq). + __mmask8 match1 = _mm512_cmpeq_epi64_mask(symb1, _mm512_and_epi64(word1, pos1)) & _mm512_cmplt_epi64_mask(icl1, all_ICL_FREE); + __mmask8 match2 = _mm512_cmpeq_epi64_mask(symb2, _mm512_and_epi64(word2, pos2)) & _mm512_cmplt_epi64_mask(icl2, all_ICL_FREE); + __mmask8 match3 = _mm512_cmpeq_epi64_mask(symb3, _mm512_and_epi64(word3, pos3)) & _mm512_cmplt_epi64_mask(icl3, all_ICL_FREE); + __mmask8 match4 = _mm512_cmpeq_epi64_mask(symb4, _mm512_and_epi64(word4, pos4)) & _mm512_cmplt_epi64_mask(icl4, all_ICL_FREE); + // for the hits, overwrite the codes with what comes from the hash table (codes for symbols of length >=3). The rest stays with what shortCodes gave. + // for the hits, overwrite the codes with what comes from the hash table (codes for symbols of length >=3). The rest stays with what shortCodes gave. + // for the hits, overwrite the codes with what comes from the hash table (codes for symbols of length >=3). The rest stays with what shortCodes gave. + // for the hits, overwrite the codes with what comes from the hash table (codes for symbols of length >=3). The rest stays with what shortCodes gave. + code1 = _mm512_mask_mov_epi64(code1, match1, _mm512_srli_epi64(icl1, 16)); + code2 = _mm512_mask_mov_epi64(code2, match2, _mm512_srli_epi64(icl2, 16)); + code3 = _mm512_mask_mov_epi64(code3, match3, _mm512_srli_epi64(icl3, 16)); + code4 = _mm512_mask_mov_epi64(code4, match4, _mm512_srli_epi64(icl4, 16)); + // write out the code byte as the first output byte. Notice that this byte may also be the escape code 255 (for escapes) coming from shortCodes. + // write out the code byte as the first output byte. Notice that this byte may also be the escape code 255 (for escapes) coming from shortCodes. + // write out the code byte as the first output byte. Notice that this byte may also be the escape code 255 (for escapes) coming from shortCodes. + // write out the code byte as the first output byte. Notice that this byte may also be the escape code 255 (for escapes) coming from shortCodes. + write1 = _mm512_or_epi64(write1, _mm512_and_epi64(code1, all_FF)); + write2 = _mm512_or_epi64(write2, _mm512_and_epi64(code2, all_FF)); + write3 = _mm512_or_epi64(write3, _mm512_and_epi64(code3, all_FF)); + write4 = _mm512_or_epi64(write4, _mm512_and_epi64(code4, all_FF)); + // zip the irrelevant 6 bytes (just stay with the 2 relevant bytes containing the 16-bits code) + // zip the irrelevant 6 bytes (just stay with the 2 relevant bytes containing the 16-bits code) + // zip the irrelevant 6 bytes (just stay with the 2 relevant bytes containing the 16-bits code) + // zip the irrelevant 6 bytes (just stay with the 2 relevant bytes containing the 16-bits code) + code1 = _mm512_and_epi64(code1, all_FFFF); + code2 = _mm512_and_epi64(code2, all_FFFF); + code3 = _mm512_and_epi64(code3, all_FFFF); + code4 = _mm512_and_epi64(code4, all_FFFF); + // write out the compressed data. It writes 8 bytes, but only 1 byte is relevant :-(or 2 bytes are, in case of an escape code) + // write out the compressed data. It writes 8 bytes, but only 1 byte is relevant :-(or 2 bytes are, in case of an escape code) + // write out the compressed data. It writes 8 bytes, but only 1 byte is relevant :-(or 2 bytes are, in case of an escape code) + // write out the compressed data. It writes 8 bytes, but only 1 byte is relevant :-(or 2 bytes are, in case of an escape code) + _mm512_i64scatter_epi64(codeBase, _mm512_and_epi64(job1, all_M19), write1, 1); + _mm512_i64scatter_epi64(codeBase, _mm512_and_epi64(job2, all_M19), write2, 1); + _mm512_i64scatter_epi64(codeBase, _mm512_and_epi64(job3, all_M19), write3, 1); + _mm512_i64scatter_epi64(codeBase, _mm512_and_epi64(job4, all_M19), write4, 1); + // increase the job1.cur field in the job with the symbol length (for this, shift away 12 bits from the code) + // increase the job2.cur field in the job with the symbol length (for this, shift away 12 bits from the code) + // increase the job3.cur field in the job with the symbol length (for this, shift away 12 bits from the code) + // increase the job4.cur field in the job with the symbol length (for this, shift away 12 bits from the code) + job1 = _mm512_add_epi64(job1, _mm512_slli_epi64(_mm512_srli_epi64(code1, FSST_LEN_BITS), 46)); + job2 = _mm512_add_epi64(job2, _mm512_slli_epi64(_mm512_srli_epi64(code2, FSST_LEN_BITS), 46)); + job3 = _mm512_add_epi64(job3, _mm512_slli_epi64(_mm512_srli_epi64(code3, FSST_LEN_BITS), 46)); + job4 = _mm512_add_epi64(job4, _mm512_slli_epi64(_mm512_srli_epi64(code4, FSST_LEN_BITS), 46)); + // increase the job1.out' field with one, or two in case of an escape code (add 1 plus the escape bit, i.e the 8th) + // increase the job2.out' field with one, or two in case of an escape code (add 1 plus the escape bit, i.e the 8th) + // increase the job3.out' field with one, or two in case of an escape code (add 1 plus the escape bit, i.e the 8th) + // increase the job4.out' field with one, or two in case of an escape code (add 1 plus the escape bit, i.e the 8th) + job1 = _mm512_add_epi64(job1, _mm512_add_epi64(all_ONE, _mm512_and_epi64(_mm512_srli_epi64(code1, 8), all_ONE))); + job2 = _mm512_add_epi64(job2, _mm512_add_epi64(all_ONE, _mm512_and_epi64(_mm512_srli_epi64(code2, 8), all_ONE))); + job3 = _mm512_add_epi64(job3, _mm512_add_epi64(all_ONE, _mm512_and_epi64(_mm512_srli_epi64(code3, 8), all_ONE))); + job4 = _mm512_add_epi64(job4, _mm512_add_epi64(all_ONE, _mm512_and_epi64(_mm512_srli_epi64(code4, 8), all_ONE))); + // test which lanes are done now (job1.cur==job1.end), cur starts at bit 46, end starts at bit 28 (the highest 2x18 bits in the job1 register) + // test which lanes are done now (job2.cur==job2.end), cur starts at bit 46, end starts at bit 28 (the highest 2x18 bits in the job2 register) + // test which lanes are done now (job3.cur==job3.end), cur starts at bit 46, end starts at bit 28 (the highest 2x18 bits in the job3 register) + // test which lanes are done now (job4.cur==job4.end), cur starts at bit 46, end starts at bit 28 (the highest 2x18 bits in the job4 register) + loadmask1 = _mm512_cmpeq_epi64_mask(_mm512_srli_epi64(job1, 46), _mm512_and_epi64(_mm512_srli_epi64(job1, 28), all_M18)); + loadmask2 = _mm512_cmpeq_epi64_mask(_mm512_srli_epi64(job2, 46), _mm512_and_epi64(_mm512_srli_epi64(job2, 28), all_M18)); + loadmask3 = _mm512_cmpeq_epi64_mask(_mm512_srli_epi64(job3, 46), _mm512_and_epi64(_mm512_srli_epi64(job3, 28), all_M18)); + loadmask4 = _mm512_cmpeq_epi64_mask(_mm512_srli_epi64(job4, 46), _mm512_and_epi64(_mm512_srli_epi64(job4, 28), all_M18)); + // calculate the amount of lanes in job1 that are done + // calculate the amount of lanes in job2 that are done + // calculate the amount of lanes in job3 that are done + // calculate the amount of lanes in job4 that are done + delta1 = _mm_popcnt_u32((int) loadmask1); + delta2 = _mm_popcnt_u32((int) loadmask2); + delta3 = _mm_popcnt_u32((int) loadmask3); + delta4 = _mm_popcnt_u32((int) loadmask4); + // write out the job state for the lanes that are done (we need the final 'job1.out' value to compute the compressed string length) + // write out the job state for the lanes that are done (we need the final 'job2.out' value to compute the compressed string length) + // write out the job state for the lanes that are done (we need the final 'job3.out' value to compute the compressed string length) + // write out the job state for the lanes that are done (we need the final 'job4.out' value to compute the compressed string length) + _mm512_mask_compressstoreu_epi64(output, loadmask1, job1); output += delta1; + _mm512_mask_compressstoreu_epi64(output, loadmask2, job2); output += delta2; + _mm512_mask_compressstoreu_epi64(output, loadmask3, job3); output += delta3; + _mm512_mask_compressstoreu_epi64(output, loadmask4, job4); output += delta4; diff --git a/cpp/src/arrow/vendored/fsst/libfsst.cpp b/cpp/src/arrow/vendored/fsst/libfsst.cpp new file mode 100644 index 000000000000..9e303018094c --- /dev/null +++ b/cpp/src/arrow/vendored/fsst/libfsst.cpp @@ -0,0 +1,651 @@ +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +#include "libfsst.hpp" + +namespace libfsst { +Symbol concat(Symbol a, Symbol b) { + Symbol s; + u32 length = a.length()+b.length(); + if (length > Symbol::maxLength) length = Symbol::maxLength; + s.set_code_len(FSST_CODE_MASK, length); + s.store_num((b.load_num() << (8*a.length())) | a.load_num()); + return s; +} +} // namespace libfsst + +namespace std { +template <> +class hash { + public: + size_t operator()(const libfsst::QSymbol& q) const { + uint64_t k = q.symbol.load_num(); + const uint64_t m = 0xc6a4a7935bd1e995; + const int r = 47; + uint64_t h = 0x8445d61a4e774912 ^ (8*m); + k *= m; + k ^= k >> r; + k *= m; + h ^= k; + h *= m; + h ^= h >> r; + h *= m; + h ^= h >> r; + return h; + } +}; +} + +namespace libfsst { +bool isEscapeCode(u16 pos) { return pos < FSST_CODE_BASE; } + +std::ostream& operator<<(std::ostream& out, const Symbol& s) { + for (u32 i=0; i line, const size_t len[], bool zeroTerminated=false) { + SymbolTable *st = new SymbolTable(), *bestTable = new SymbolTable(); + int bestGain = (int) -FSST_SAMPLEMAXSZ; // worst case (everything exception) + size_t sampleFrac = 128; + + // start by determining the terminator. We use the (lowest) most infrequent byte as terminator + st->zeroTerminated = zeroTerminated; + if (zeroTerminated) { + st->terminator = 0; // except in case of zeroTerminated mode, then byte 0 is terminator regardless frequency + } else { + u16 byteHisto[256]; + memset(byteHisto, 0, sizeof(byteHisto)); + for(size_t i=0; iterminator = 256; + while(i-- > 0) { + if (byteHisto[i] > minSize) continue; + st->terminator = i; + minSize = byteHisto[i]; + } + } + assert(st->terminator != 256); + + // a random number between 0 and 128 + auto rnd128 = [&](size_t i) { return 1 + (FSST_HASH((i+1UL)*sampleFrac)&127); }; + + // compress sample, and compute (pair-)frequencies + auto compressCount = [&](SymbolTable *st, Counters &counters) { // returns gain + int gain = 0; + + for(size_t i=0; i sampleFrac) continue; + } + if (cur < end) { + u16 code2 = 255, code1 = st->findLongestSymbol(cur, end); + cur += st->symbols[code1].length(); + gain += (int) (st->symbols[code1].length()-(1+isEscapeCode(code1))); + while (true) { + // count single symbol (i.e. an option is not extending it) + counters.count1Inc(code1); + + // as an alternative, consider just using the next byte.. + if (st->symbols[code1].length() != 1) // .. but do not count single byte symbols doubly + counters.count1Inc(*start); + + if (cur==end) { + break; + } + + // now match a new symbol + start = cur; + if (curhashTabSize-1); + Symbol s = st->hashTab[idx]; + code2 = st->shortCodes[word & 0xFFFF] & FSST_CODE_MASK; + word &= (0xFFFFFFFFFFFFFFFF >> (u8) s.icl); + if ((s.icl < FSST_ICL_FREE) & (s.load_num() == word)) { + code2 = s.code(); + cur += s.length(); + } else if (code2 >= FSST_CODE_BASE) { + cur += 2; + } else { + code2 = st->byteCodes[word & 0xFF] & FSST_CODE_MASK; + cur += 1; + } + } else { + code2 = st->findLongestSymbol(cur, end); + cur += st->symbols[code2].length(); + } + + // compute compressed output size + gain += ((int) (cur-start))-(1+isEscapeCode(code2)); + + if (sampleFrac < 128) { // no need to count pairs in final round + // consider the symbol that is the concatenation of the two last symbols + counters.count2Inc(code1, code2); + + // as an alternative, consider just extending with the next byte.. + if ((cur-start) > 1) // ..but do not count single byte extensions doubly + counters.count2Inc(code1, *start); + } + code1 = code2; + } + } + } + return gain; + }; + + auto makeTable = [&](SymbolTable *st, Counters &counters) { + // hashmap of c (needed because we can generate duplicate candidates) + unordered_set cands; + + // artificially make terminater the most frequent symbol so it gets included + u16 terminator = st->nSymbols?FSST_CODE_BASE:st->terminator; + counters.count1Set(terminator,65535); + + auto addOrInc = [&](unordered_set &cands, Symbol s, u64 count) { + if (count < (5*sampleFrac)/128) return; // improves both compression speed (less candidates), but also quality!! + QSymbol q; + q.symbol = s; + q.gain = count * s.length(); + auto it = cands.find(q); + if (it != cands.end()) { + q.gain += (*it).gain; + cands.erase(*it); + } + cands.insert(q); + }; + + // add candidate symbols based on counted frequency + for (u32 pos1=0; pos1nSymbols; pos1++) { + u32 cnt1 = counters.count1GetNext(pos1); // may advance pos1!! + if (!cnt1) continue; + + // heuristic: promoting single-byte symbols (*8) helps reduce exception rates and increases [de]compression speed + Symbol s1 = st->symbols[pos1]; + addOrInc(cands, s1, ((s1.length()==1)?8LL:1LL)*cnt1); + + if (sampleFrac >= 128 || // last round we do not create new (combined) symbols + s1.length() == Symbol::maxLength || // symbol cannot be extended + s1.val.str[0] == st->terminator) { // multi-byte symbols cannot contain the terminator byte + continue; + } + for (u32 pos2=0; pos2nSymbols; pos2++) { + u32 cnt2 = counters.count2GetNext(pos1, pos2); // may advance pos2!! + if (!cnt2) continue; + + // create a new symbol + Symbol s2 = st->symbols[pos2]; + Symbol s3 = concat(s1, s2); + if (s2.val.str[0] != st->terminator) // multi-byte symbols cannot contain the terminator byte + addOrInc(cands, s3, cnt2); + } + } + + // insert candidates into priority queue (by gain) + auto cmpGn = [](const QSymbol& q1, const QSymbol& q2) { return (q1.gain < q2.gain) || (q1.gain == q2.gain && q1.symbol.load_num() > q2.symbol.load_num()); }; + priority_queue,decltype(cmpGn)> pq(cmpGn); + for (auto& q : cands) + pq.push(q); + + // Create new symbol map using best candidates + st->clear(); + while (st->nSymbols < 255 && !pq.empty()) { + QSymbol q = pq.top(); + pq.pop(); + st->add(q.symbol); + } + }; + + u8 bestCounters[512*sizeof(u16)]; +#ifdef NONOPT_FSST + for(size_t frac : {127, 127, 127, 127, 127, 127, 127, 127, 127, 128}) { + sampleFrac = frac; +#else + for(sampleFrac=8; true; sampleFrac += 30) { +#endif + memset(&counters, 0, sizeof(Counters)); + long gain = compressCount(st, counters); + if (gain >= bestGain) { // a new best solution! + counters.backup1(bestCounters); + *bestTable = *st; bestGain = gain; + } + if (sampleFrac >= 128) break; // we do 5 rounds (sampleFrac=8,38,68,98,128) + makeTable(st, counters); + } + delete st; + counters.restore1(bestCounters); + makeTable(bestTable, counters); + bestTable->finalize(zeroTerminated); // renumber codes for more efficient compression + return bestTable; +} + +#ifndef NONOPT_FSST +static inline size_t compressSIMD(SymbolTable &symbolTable, u8* symbolBase, size_t nlines, const size_t len[], const u8* line[], size_t size, u8* dst, size_t lenOut[], u8* strOut[], int unroll) { + size_t curLine = 0, inOff = 0, outOff = 0, batchPos = 0, empty = 0, budget = size; + u8 *lim = dst + size, *codeBase = symbolBase + (1<<18); // 512KB temp space for compressing 512 strings + SIMDjob input[512]; // combined offsets of input strings (cur,end), and string #id (pos) and output (dst) pointer + SIMDjob output[512]; // output are (pos:9,dst:19) end pointers (compute compressed length from this) + size_t jobLine[512]; // for which line in the input sequence was this job (needed because we may split a line into multiple jobs) + + while (curLine < nlines && outOff <= (1<<19)) { + size_t prevLine = curLine, chunk, curOff = 0; + + // bail out if the output buffer cannot hold the compressed next string fully + if (((len[curLine]-curOff)*2 + 7) > budget) break; // see below for the +7 + else budget -= (len[curLine]-curOff)*2; + + strOut[curLine] = (u8*) 0; + lenOut[curLine] = 0; + + do { + do { + chunk = len[curLine] - curOff; + if (chunk > 511) { + chunk = 511; // large strings need to be chopped up into segments of 511 bytes + } + // create a job in this batch + SIMDjob job; + job.cur = inOff; + job.end = job.cur + chunk; + job.pos = batchPos; + job.out = outOff; + + // worst case estimate for compressed size (+7 is for the scatter that writes extra 7 zeros) + outOff += 7 + 2*(size_t)(job.end - job.cur); // note, total size needed is 512*(511*2+7) bytes. + if (outOff > (1<<19)) break; // simdbuf may get full, stop before this chunk + + // register job in this batch + input[batchPos] = job; + jobLine[batchPos] = curLine; + + if (chunk == 0) { + empty++; // detect empty chunks -- SIMD code cannot handle empty strings, so they need to be filtered out + } else { + // copy string chunk into temp buffer + memcpy(symbolBase + inOff, line[curLine] + curOff, chunk); + inOff += chunk; + curOff += chunk; + symbolBase[inOff++] = (u8) symbolTable.terminator; // write an extra char at the end that will not be encoded + } + if (++batchPos == 512) break; + } while(curOff < len[curLine]); + + if ((batchPos == 512) || (outOff > (1<<19)) || (++curLine >= nlines) || (((len[curLine])*2 + 7) > budget)) { // cannot accumulate more? + if (batchPos-empty >= 32) { // if we have enough work, fire off fsst_compressAVX512 (32 is due to max 4x8 unrolling) + // radix-sort jobs on length (longest string first) + // -- this provides best load balancing and allows to skip empty jobs at the end + u16 sortpos[513]; + memset(sortpos, 0, sizeof(sortpos)); + + // calculate length histo + for(size_t i=0; i> (u8) s.icl); + if ((s.icl < FSST_ICL_FREE) && s.load_num() == word) { + *out++ = (u8) s.code(); cur += s.length(); + } else { + // could be a 2-byte or 1-byte code, or miss + // handle everything with predication + *out = (u8) code; + out += 1+((code&FSST_CODE_BASE)>>8); + cur += (code>>FSST_LEN_BITS); + } + } + job.out = out - codeBase; + } + // postprocess job info + job.cur = 0; + job.end = job.out - input[job.pos].out; // misuse .end field as compressed size + job.out = input[job.pos].out; // reset offset to start of encoded string + input[job.pos] = job; + } + + // copy out the result data + for(size_t i=0; i> (u8) s.icl); + if ((s.icl < FSST_ICL_FREE) && s.load_num() == word) { + *out++ = (u8) s.code(); cur += s.length(); + } else if (avoidBranch) { + // could be a 2-byte or 1-byte code, or miss + // handle everything with predication + *out = (u8) code; + out += 1+((code&FSST_CODE_BASE)>>8); + cur += (code>>FSST_LEN_BITS); + } else if ((u8) code < byteLim) { + // 2 byte code after checking there is no longer pattern + *out++ = (u8) code; cur += 2; + } else { + // 1 byte code or miss. + *out = (u8) code; + out += 1+((code&FSST_CODE_BASE)>>8); // predicated - tested with a branch, that was always worse + cur++; + } + } + } + }; + + for(curLine=0; curLine 511) { + chunk = 511; // we need to compress in chunks of 511 in order to be byte-compatible with simd-compressed FSST + } + if ((2*chunk+7) > (size_t) (lim-out)) { + return curLine; // out of memory + } + // copy the string to the 511-byte buffer + memcpy(buf, cur, chunk); + buf[chunk] = (u8) symbolTable.terminator; + cur = buf; + end = cur + chunk; + + // based on symboltable stats, choose a variant that is nice to the branch predictor + if (noSuffixOpt) { + compressVariant(true,false); + } else if (avoidBranch) { + compressVariant(false,true); + } else { + compressVariant(false, false); + } + } while((curOff += chunk) < lenIn[curLine]); + lenOut[curLine] = (size_t) (out - strOut[curLine]); + } + return curLine; +} + +#define FSST_SAMPLELINE ((size_t) 512) + +// quickly select a uniformly random set of lines such that we have between [FSST_SAMPLETARGET,FSST_SAMPLEMAXSZ) string bytes +vector makeSample(u8* sampleBuf, const u8* strIn[], const size_t **lenRef, size_t nlines) { + size_t totSize = 0; + const size_t *lenIn = *lenRef; + vector sample; + + for(size_t i=0; i sample = makeSample(sampleBuf, strIn, &sampleLen, n?n:1); // careful handling of input to get a right-size and representative sample + Encoder *encoder = new Encoder(); + encoder->symbolTable = shared_ptr(buildSymbolTable(encoder->counters, sample, sampleLen, zeroTerminated)); + if (sampleLen != lenIn) delete[] sampleLen; + delete[] sampleBuf; + return (fsst_encoder_t*) encoder; +} + +/* create another encoder instance, necessary to do multi-threaded encoding using the same symbol table */ +extern "C" fsst_encoder_t* fsst_duplicate(fsst_encoder_t *encoder) { + Encoder *e = new Encoder(); + e->symbolTable = ((Encoder*)encoder)->symbolTable; // it is a shared_ptr + return (fsst_encoder_t*) e; +} + +// export a symbol table in compact format. +extern "C" u32 fsst_export(fsst_encoder_t *encoder, u8 *buf) { + Encoder *e = (Encoder*) encoder; + // In ->version there is a versionnr, but we hide also suffixLim/terminator/nSymbols there. + // This is sufficient in principle to *reconstruct* a fsst_encoder_t from a fsst_decoder_t + // (such functionality could be useful to append compressed data to an existing block). + // + // However, the hash function in the encoder hash table is endian-sensitive, and given its + // 'lossy perfect' hashing scheme is *unable* to contain other-endian-produced symbol tables. + // Doing a endian-conversion during hashing will be slow and self-defeating. + // + // Overall, we could support reconstructing an encoder for incremental compression, but + // should enforce equal-endianness. Bit of a bummer. Not going there now. + // + // The version field is now there just for future-proofness, but not used yet + + // version allows keeping track of fsst versions, track endianness, and encoder reconstruction + u64 version = (FSST_VERSION << 32) | // version is 24 bits, most significant byte is 0 + (((u64) e->symbolTable->suffixLim) << 24) | + (((u64) e->symbolTable->terminator) << 16) | + (((u64) e->symbolTable->nSymbols) << 8) | + FSST_ENDIAN_MARKER; // least significant byte is nonzero + + version = swap64_if_be(version); // ensure version is little-endian encoded + + /* do not assume unaligned reads here */ + memcpy(buf, &version, 8); + buf[8] = e->symbolTable->zeroTerminated; + for(u32 i=0; i<8; i++) + buf[9+i] = (u8) e->symbolTable->lenHisto[i]; + u32 pos = 17; + + // emit only the used bytes of the symbols + for(u32 i = e->symbolTable->zeroTerminated; i < e->symbolTable->nSymbols; i++) + for(u32 j = 0; j < e->symbolTable->symbols[i].length(); j++) + buf[pos++] = e->symbolTable->symbols[i].val.str[j]; // serialize used symbol bytes + + return pos; // length of what was serialized +} + +#define FSST_CORRUPT 32774747032022883 /* 7-byte number in little endian containing "corrupt" */ + +extern "C" u32 fsst_import(fsst_decoder_t *decoder, u8 const *buf) { + u64 version = 0; + u32 code, pos = 17; + u8 lenHisto[8]; + + // version field (first 8 bytes) is now there just for future-proofness, unused still (skipped) + memcpy(&version, buf, 8); + version = swap64_if_be(version); // version is always little-endian encoded + + if ((version>>32) != FSST_VERSION) return 0; + decoder->zeroTerminated = buf[8]&1; + memcpy(lenHisto, buf+9, 8); + + // in case of zero-terminated, first symbol is "" (zero always, may be overwritten) + decoder->len[0] = 1; + decoder->symbol[0] = 0; + + // we use lenHisto[0] as 1-byte symbol run length (at the end) + code = decoder->zeroTerminated; + if (decoder->zeroTerminated) lenHisto[0]--; // if zeroTerminated, then symbol "" aka 1-byte code=0, is not stored at the end + + // now get all symbols from the buffer + for(u32 l=1; l<=8; l++) { /* l = 1,2,3,4,5,6,7,8 */ + for(u32 i=0; i < lenHisto[(l&7) /* 1,2,3,4,5,6,7,0 */]; i++, code++) { + decoder->len[code] = (l&7)+1; /* len = 2,3,4,5,6,7,8,1 */ + decoder->symbol[code] = 0; + for(u32 j=0; jlen[code]; j++) + ((u8*) &decoder->symbol[code])[j] = buf[pos++]; // note this enforces 'little endian' symbols + } + } + if (decoder->zeroTerminated) lenHisto[0]++; + + // fill unused symbols with text "corrupt". Gives a chance to detect corrupted code sequences (if there are unused symbols). + while(code<255) { + decoder->symbol[code] = FSST_CORRUPT; + decoder->len[code++] = 8; + } + return pos; +} + +// runtime check for simd +inline size_t _compressImpl(Encoder *e, size_t nlines, const size_t lenIn[], const u8 *strIn[], size_t size, u8 *output, size_t *lenOut, u8 *strOut[], bool noSuffixOpt, bool avoidBranch, int simd) { +#ifndef NONOPT_FSST + if (simd && fsst_hasAVX512()) + return compressSIMD(*e->symbolTable, e->simdbuf, nlines, lenIn, strIn, size, output, lenOut, strOut, simd); +#endif + (void) simd; + return compressBulk(*e->symbolTable, nlines, lenIn, strIn, size, output, lenOut, strOut, noSuffixOpt, avoidBranch); +} +size_t compressImpl(Encoder *e, size_t nlines, const size_t lenIn[], const u8 *strIn[], size_t size, u8 *output, size_t *lenOut, u8 *strOut[], bool noSuffixOpt, bool avoidBranch, int simd) { + return _compressImpl(e, nlines, lenIn, strIn, size, output, lenOut, strOut, noSuffixOpt, avoidBranch, simd); +} + +// adaptive choosing of scalar compression method based on symbol length histogram +inline size_t _compressAuto(Encoder *e, size_t nlines, const size_t lenIn[], const u8 *strIn[], size_t size, u8 *output, size_t *lenOut, u8 *strOut[], int simd) { + bool avoidBranch = false, noSuffixOpt = false; + if (100*e->symbolTable->lenHisto[1] > 65*e->symbolTable->nSymbols && 100*e->symbolTable->suffixLim > 95*e->symbolTable->lenHisto[1]) { + noSuffixOpt = true; + } else if ((e->symbolTable->lenHisto[0] > 24 && e->symbolTable->lenHisto[0] < 92) && + (e->symbolTable->lenHisto[0] < 43 || e->symbolTable->lenHisto[6] + e->symbolTable->lenHisto[7] < 29) && + (e->symbolTable->lenHisto[0] < 72 || e->symbolTable->lenHisto[2] < 72)) { + avoidBranch = true; + } + return _compressImpl(e, nlines, lenIn, strIn, size, output, lenOut, strOut, noSuffixOpt, avoidBranch, simd); +} +size_t compressAuto(Encoder *e, size_t nlines, const size_t lenIn[], const u8 *strIn[], size_t size, u8 *output, size_t *lenOut, u8 *strOut[], int simd) { + return _compressAuto(e, nlines, lenIn, strIn, size, output, lenOut, strOut, simd); +} +} // namespace libfsst + +using namespace libfsst; +// the main compression function (everything automatic) +extern "C" size_t fsst_compress(fsst_encoder_t *encoder, size_t nlines, const size_t lenIn[], const u8 *strIn[], size_t size, u8 *output, size_t *lenOut, u8 *strOut[]) { + // to be faster than scalar, simd needs 64 lines or more of length >=12; or fewer lines, but big ones (totLen > 32KB) + size_t totLen = accumulate(lenIn, lenIn+nlines, 0); + int simd = totLen > nlines*12 && (nlines > 64 || totLen > (size_t) 1<<15); + return _compressAuto((Encoder*) encoder, nlines, lenIn, strIn, size, output, lenOut, strOut, 3*simd); +} + +/* deallocate encoder */ +extern "C" void fsst_destroy(fsst_encoder_t* encoder) { + Encoder *e = (Encoder*) encoder; + delete e; +} + +/* very lazy implementation relying on export and import */ +extern "C" fsst_decoder_t fsst_decoder(fsst_encoder_t *encoder) { + u8 buf[sizeof(fsst_decoder_t)]; + u32 cnt1 = fsst_export(encoder, buf); + fsst_decoder_t decoder; + u32 cnt2 = fsst_import(&decoder, buf); + assert(cnt1 == cnt2); (void) cnt1; (void) cnt2; + return decoder; +} diff --git a/cpp/src/arrow/vendored/fsst/libfsst.hpp b/cpp/src/arrow/vendored/fsst/libfsst.hpp new file mode 100644 index 000000000000..e50f513a03cd --- /dev/null +++ b/cpp/src/arrow/vendored/fsst/libfsst.hpp @@ -0,0 +1,471 @@ +// this software is distributed under the MIT License (http://www.opensource.org/licenses/MIT): +// +// Copyright 2018-2020, CWI, TU Munich, FSU Jena +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, +// merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// You can contact the authors via the FSST source repository : https://github.com/cwida/fsst +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +#include "fsst.h" // the official FSST API -- also usable by C mortals + +/* unsigned integers */ +namespace libfsst { +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; +} // namespace libfsst + +#if UINTPTR_MAX == 0xffffffffU +// We're on a 32-bit platform +#define NONOPT_FSST +#endif + +#define FSST_ENDIAN_MARKER ((u64) 1) +#define FSST_VERSION_20190218 20190218 +#define FSST_VERSION ((u64) FSST_VERSION_20190218) + +// "symbols" are character sequences (up to 8 bytes) +// A symbol is compressed into a "code" of, in principle, one byte. But, we added an exception mechanism: +// byte 255 followed by byte X represents the single-byte symbol X. Its code is 256+X. + +// we represent codes in u16 (not u8). 12 bits code (of which 10 are used), 4 bits length +#define FSST_LEN_BITS 12 +#define FSST_CODE_BITS 9 +#define FSST_CODE_BASE 256UL /* first 256 codes [0,255] are pseudo codes: escaped bytes */ +#define FSST_CODE_MAX (1UL<=8) { + len = 8; + memcpy(val.str, input, 8); + } else { + memcpy(val.str, input, len); + } + set_code_len(FSST_CODE_MAX, len); + } + void set_code_len(u32 code, u32 len) { icl = (len<<28)|(code<<16)|((8-len)*8); } + + u64 load_num() const { return swap64_if_be(val.num); } + void store_num(u64 v) { val.num = swap64_if_be(v); } + + u32 length() const { return (u32) (icl >> 28); } + u16 code() const { return (icl >> 16) & FSST_CODE_MASK; } + u32 ignoredBits() const { return (u32) icl; } + + u8 first() const { assert( length() >= 1); return 0xFF & load_num(); } + u16 first2() const { assert( length() >= 2); return 0xFFFF & load_num(); } + +#define FSST_HASH_LOG2SIZE 10 +#define FSST_HASH_PRIME 2971215073LL +#define FSST_SHIFT 15 +#define FSST_HASH(w) (((w)*FSST_HASH_PRIME)^(((w)*FSST_HASH_PRIME)>>FSST_SHIFT)) + size_t hash() const { size_t v = 0xFFFFFF & load_num(); return FSST_HASH(v); } // hash on the next 3 bytes +}; + +// Symbol that can be put in a queue, ordered on gain +struct QSymbol{ + Symbol symbol; + mutable u32 gain; // mutable because gain value should be ignored in find() on unordered_set of QSymbols + bool operator==(const QSymbol& other) const { return symbol.val.num == other.symbol.val.num && symbol.length() == other.symbol.length(); } +}; + +// we construct FSST symbol tables using a random sample of about 16KB (1<<14) +#define FSST_SAMPLETARGET (1<<14) +#define FSST_SAMPLEMAXSZ ((long) 2*FSST_SAMPLETARGET) + +// two phases of compression, before and after optimize(): +// +// (1) to encode values we probe (and maintain) three datastructures: +// - u16 byteCodes[256] array at the position of the next byte (s.length==1) +// - u16 shortCodes[65536] array at the position of the next twobyte pattern (s.length==2) +// - Symbol hashtable[1024] (keyed by the next three bytes, ie for s.length>2), +// this search will yield a u16 code, it points into Symbol symbols[]. You always find a hit, because the first 256 codes are +// pseudo codes representing a single byte these will become escapes) +// +// (2) when we finished looking for the best symbol table we call optimize() to reshape it: +// - it renumbers the codes by length (first symbols of length 2,3,4,5,6,7,8; then 1 (starting from byteLim are symbols of length 1) +// length 2 codes for which no longer suffix symbol exists (< suffixLim) come first among the 2-byte codes +// (allows shortcut during compression) +// - for each two-byte combination, in all unused slots of shortCodes[], it enters the byteCode[] of the symbol corresponding +// to the first byte (if such a single-byte symbol exists). This allows us to just probe the next two bytes (if there is only one +// byte left in the string, there is still a terminator-byte added during compression) in shortCodes[]. That is, byteCodes[] +// and its codepath is no longer required. This makes compression faster. The reason we use byteCodes[] during symbolTable construction +// is that adding a new code/symbol is expensive (you have to touch shortCodes[] in 256 places). This optimization was +// hence added to make symbolTable construction faster. +// +// this final layout allows for the fastest compression code, only currently present in compressBulk + +// in the hash table, the icl field contains (low-to-high) ignoredBits:16,code:12,length:4 +#define FSST_ICL_FREE ((15<<28)|(((u32)FSST_CODE_MASK)<<16)) // high bits of icl (len=8,code=FSST_CODE_MASK) indicates free bucket + +// ignoredBits is (8-length)*8, which is the amount of high bits to zero in the input word before comparing with the hashtable key +// ..it could of course be computed from len during lookup, but storing it precomputed in some loose bits is faster +// +// the gain field is only used in the symbol queue that sorts symbols on gain + +struct SymbolTable { + static const u32 hashTabSize = 1<> (u8) s.icl)); + return true; + } + bool add(Symbol s) { + assert(FSST_CODE_BASE + nSymbols < FSST_CODE_MAX); + u32 len = s.length(); + s.set_code_len(FSST_CODE_BASE + nSymbols, len); + if (len == 1) { + byteCodes[s.first()] = FSST_CODE_BASE + nSymbols + (1<> ((u8) hashTab[idx].icl)))) { + return (hashTab[idx].icl>>16) & FSST_CODE_MASK; // matched a long symbol + } + if (s.length() >= 2) { + u16 code = shortCodes[s.first2()] & FSST_CODE_MASK; + if (code >= FSST_CODE_BASE) return code; + } + return byteCodes[s.first()] & FSST_CODE_MASK; + } + u16 findLongestSymbol(const u8* cur, const u8* end) const { + return findLongestSymbol(Symbol(cur,end)); // represent the string as a temporary symbol + } + + // rationale for finalize: + // - during symbol table construction, we may create more than 256 codes, but bring it down to max 255 in the last makeTable() + // consequently we needed more than 8 bits during symbol table contruction, but can simplify the codes to single bytes in finalize() + // (this feature is in fact lo longer used, but could still be exploited: symbol construction creates no more than 255 symbols in each pass) + // - we not only reduce the amount of codes to <255, but also *reorder* the symbols and renumber their codes, for higher compression perf. + // we renumber codes so they are grouped by length, to allow optimized scalar string compression (byteLim and suffixLim optimizations). + // - we make the use of byteCode[] no longer necessary by inserting single-byte codes in the free spots of shortCodes[] + // Using shortCodes[] only makes compression faster. When creating the symbolTable, however, using shortCodes[] for the single-byte + // symbols is slow, as each insert touches 256 positions in it. This optimization was added when optimizing symbolTable construction time. + // + // In all, we change the layout and coding, as follows.. + // + // before finalize(): + // - The real symbols are symbols[256..256+nSymbols>. As we may have nSymbols > 255 + // - The first 256 codes are pseudo symbols (all escaped bytes) + // + // after finalize(): + // - table layout is symbols[0..nSymbols>, with nSymbols < 256. + // - Real codes are [0,nSymbols>. 8-th bit not set. + // - Escapes in shortCodes have the 8th bit set (value: 256+255=511). 255 because the code to be emitted is the escape byte 255 + // - symbols are grouped by length: 2,3,4,5,6,7,8, then 1 (single-byte codes last) + // the two-byte codes are split in two sections: + // - first section contains codes for symbols for which there is no longer symbol (no suffix). It allows an early-out during compression + // + // finally, shortCodes[] is modified to also encode all single-byte symbols (hence byteCodes[] is not required on a critical path anymore). + // + void finalize(u8 zeroTerminated) { + assert(nSymbols <= 255); + u8 newCode[256], rsum[8], byteLim = nSymbols - (lenHisto[0] - zeroTerminated); + + // compute running sum of code lengths (starting offsets for each length) + rsum[0] = byteLim; // 1-byte codes are highest + rsum[1] = zeroTerminated; + for(u32 i=1; i<7; i++) + rsum[i+1] = rsum[i] + lenHisto[i]; + + // determine the new code for each symbol, ordered by length (and splitting 2byte symbols into two classes around suffixLim) + suffixLim = rsum[1]; + symbols[newCode[0] = 0] = symbols[256]; // keep symbol 0 in place (for zeroTerminated cases only) + + for(u32 i=zeroTerminated, j=rsum[2]; i 1 && first2 == s2.first2()) // test if symbol k is a suffix of s + opt = 0; + } + newCode[i] = opt?suffixLim++:--j; // symbols without a larger suffix have a code < suffixLim + } else + newCode[i] = rsum[len-1]++; + s1.set_code_len(newCode[i],len); + symbols[newCode[i]] = s1; + } + // renumber the codes in byteCodes[] + for(u32 i=0; i<256; i++) + if ((byteCodes[i] & FSST_CODE_MASK) >= FSST_CODE_BASE) + byteCodes[i] = newCode[(u8) byteCodes[i]] + (1 << FSST_LEN_BITS); + else + byteCodes[i] = 511 + (1 << FSST_LEN_BITS); + + // renumber the codes in shortCodes[] + for(u32 i=0; i<65536; i++) + if ((shortCodes[i] & FSST_CODE_MASK) >= FSST_CODE_BASE) + shortCodes[i] = newCode[(u8) shortCodes[i]] + (shortCodes[i] & (15 << FSST_LEN_BITS)); + else + shortCodes[i] = byteCodes[i&0xFF]; + + // replace the symbols in the hash table + for(u32 i=0; i>8; + } + void count1Inc(u32 pos1) { + if (!count1Low[pos1]++) // increment high early (when low==0, not when low==255). This means (high > 0) <=> (cnt > 0) + count1High[pos1]++; //(0,0)->(1,1)->..->(255,1)->(0,1)->(1,2)->(2,2)->(3,2)..(255,2)->(0,2)->(1,3)->(2,3)... + } + void count2Inc(u32 pos1, u32 pos2) { + if (!count2Low[pos1][pos2]++) // increment high early (when low==0, not when low==255). This means (high > 0) <=> (cnt > 0) + // inc 4-bits high counter with 1<<0 (1) or 1<<4 (16) -- depending on whether pos2 is even or odd, repectively + count2High[pos1][(pos2)>>1] += 1 << (((pos2)&1)<<2); // we take our chances with overflow.. (4K maxval, on a 8K sample) + } + u32 count1GetNext(u32 &pos1) { // note: we will advance pos1 to the next nonzero counter in register range + // read 16-bits single symbol counter, split into two 8-bits numbers (count1Low, count1High), while skipping over zeros + u64 high = fsst_unaligned_load(&count1High[pos1]); // note: this reads 8 subsequent counters [pos1..pos1+7] + + u32 zero = high?(__builtin_ctzl(high)>>3):7UL; // number of zero bytes + high = (high >> (zero << 3)) & 255; // advance to nonzero counter + if (((pos1 += zero) >= FSST_CODE_MAX) || !high) // SKIP! advance pos2 + return 0; // all zero + + u32 low = count1Low[pos1]; + if (low) high--; // high is incremented early and low late, so decrement high (unless low==0) + return (u32) ((high << 8) + low); + } + u32 count2GetNext(u32 pos1, u32 &pos2) { // note: we will advance pos2 to the next nonzero counter in register range + // read 12-bits pairwise symbol counter, split into low 8-bits and high 4-bits number while skipping over zeros + u64 high = fsst_unaligned_load(&count2High[pos1][pos2>>1]); // note: this reads 16 subsequent counters [pos2..pos2+15] + high >>= ((pos2&1) << 2); // odd pos2: ignore the lowest 4 bits & we see only 15 counters + + u32 zero = high?(__builtin_ctzl(high)>>2):(15UL-(pos2&1UL)); // number of zero 4-bits counters + high = (high >> (zero << 2)) & 15; // advance to nonzero counter + if (((pos2 += zero) >= FSST_CODE_MAX) || !high) // SKIP! advance pos2 + return 0UL; // all zero + + u32 low = count2Low[pos1][pos2]; + if (low) high--; // high is incremented early and low late, so decrement high (unless low==0) + return (u32) ((high << 8) + low); + } + void backup1(u8 *buf) { + memcpy(buf, count1High, FSST_CODE_MAX); + memcpy(buf+FSST_CODE_MAX, count1Low, FSST_CODE_MAX); + } + void restore1(u8 *buf) { + memcpy(count1High, buf, FSST_CODE_MAX); + memcpy(count1Low, buf+FSST_CODE_MAX, FSST_CODE_MAX); + } +}; +#endif + + +#define FSST_BUFSZ (3<<19) // 768KB + +// an encoder is a symbolmap plus some bufferspace, needed during map construction as well as compression +struct Encoder { + shared_ptr symbolTable; // symbols, plus metadata and data structures for quick compression (shortCode,hashTab, etc) + union { + Counters counters; // for counting symbol occurences during map construction + u8 simdbuf[FSST_BUFSZ]; // for compression: SIMD string staging area 768KB = 256KB in + 512KB out (worst case for 256KB in) + }; +}; + +// job control integer representable in one 64bits SIMD lane: cur/end=input, out=output, pos=which string (2^9=512 per call) +struct SIMDjob { + u64 out:19,pos:9,end:18,cur:18; // cur/end is input offsets (2^18=256KB), out is output offset (2^19=512KB) +}; + +extern bool +fsst_hasAVX512(); // runtime check for avx512 capability + +extern size_t +fsst_compressAVX512( + SymbolTable &symbolTable, + u8* codeBase, // IN: base address for codes, i.e. compression output (points to simdbuf+256KB) + u8* symbolBase, // IN: base address for string bytes, i.e. compression input (points to simdbuf) + SIMDjob* input, // IN: input array (size n) with job information: what to encode, where to store it. + SIMDjob* output, // OUT: output array (size n) with job information: how much got encoded, end output pointer. + size_t n, // IN: size of arrays input and output (should be max 512) + size_t unroll); // IN: degree of SIMD unrolling + +// C++ fsst-compress function with some more control of how the compression happens (algorithm flavor, simd unroll degree) +size_t compressImpl(Encoder *encoder, size_t n, size_t lenIn[], u8 *strIn[], size_t size, u8 * output, size_t *lenOut, u8 *strOut[], bool noSuffixOpt, bool avoidBranch, int simd); +size_t compressAuto(Encoder *encoder, size_t n, size_t lenIn[], u8 *strIn[], size_t size, u8 * output, size_t *lenOut, u8 *strOut[], int simd); +} // namespace libfsst diff --git a/cpp/src/generated/parquet_types.cpp b/cpp/src/generated/parquet_types.cpp index cf8debb79e73..bef25eeceac9 100644 --- a/cpp/src/generated/parquet_types.cpp +++ b/cpp/src/generated/parquet_types.cpp @@ -467,7 +467,15 @@ int _kEncodingValues[] = { * Added in 2.8 for FLOAT and DOUBLE. * Support for INT32, INT64 and FIXED_LEN_BYTE_ARRAY added in 2.11. */ - Encoding::BYTE_STREAM_SPLIT + Encoding::BYTE_STREAM_SPLIT, + /** + * Adaptive Lossless floating-Point encoding. Reserved by the Parquet format. + */ + Encoding::ALP, + /** + * Fast Static Symbol Table encoding for BYTE_ARRAY values. + */ + Encoding::FSST }; const char* _kEncodingNames[] = { /** @@ -529,9 +537,17 @@ const char* _kEncodingNames[] = { * Added in 2.8 for FLOAT and DOUBLE. * Support for INT32, INT64 and FIXED_LEN_BYTE_ARRAY added in 2.11. */ - "BYTE_STREAM_SPLIT" + "BYTE_STREAM_SPLIT", + /** + * Adaptive Lossless floating-Point encoding. Reserved by the Parquet format. + */ + "ALP", + /** + * Fast Static Symbol Table encoding for BYTE_ARRAY values. + */ + "FSST" }; -const std::map _Encoding_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(9, _kEncodingValues, _kEncodingNames), ::apache::thrift::TEnumIterator(-1, nullptr, nullptr)); +const std::map _Encoding_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(11, _kEncodingValues, _kEncodingNames), ::apache::thrift::TEnumIterator(-1, nullptr, nullptr)); std::ostream& operator<<(std::ostream& out, const Encoding::type& val) { std::map::const_iterator it = _Encoding_VALUES_TO_NAMES.find(val); @@ -597,15 +613,17 @@ int _kPageTypeValues[] = { PageType::DATA_PAGE, PageType::INDEX_PAGE, PageType::DICTIONARY_PAGE, - PageType::DATA_PAGE_V2 + PageType::DATA_PAGE_V2, + PageType::SYMBOL_TABLE_PAGE }; const char* _kPageTypeNames[] = { "DATA_PAGE", "INDEX_PAGE", "DICTIONARY_PAGE", - "DATA_PAGE_V2" + "DATA_PAGE_V2", + "SYMBOL_TABLE_PAGE" }; -const std::map _PageType_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(4, _kPageTypeValues, _kPageTypeNames), ::apache::thrift::TEnumIterator(-1, nullptr, nullptr)); +const std::map _PageType_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(5, _kPageTypeValues, _kPageTypeNames), ::apache::thrift::TEnumIterator(-1, nullptr, nullptr)); std::ostream& operator<<(std::ostream& out, const PageType::type& val) { std::map::const_iterator it = _PageType_VALUES_TO_NAMES.find(val); @@ -626,6 +644,33 @@ std::string to_string(const PageType::type& val) { } } +int _kSymbolTableTypeValues[] = { + SymbolTableType::FSST +}; +const char* _kSymbolTableTypeNames[] = { + "FSST" +}; +const std::map _SymbolTableType_VALUES_TO_NAMES(::apache::thrift::TEnumIterator(1, _kSymbolTableTypeValues, _kSymbolTableTypeNames), ::apache::thrift::TEnumIterator(-1, nullptr, nullptr)); + +std::ostream& operator<<(std::ostream& out, const SymbolTableType::type& val) { + std::map::const_iterator it = _SymbolTableType_VALUES_TO_NAMES.find(val); + if (it != _SymbolTableType_VALUES_TO_NAMES.end()) { + out << it->second; + } else { + out << static_cast(val); + } + return out; +} + +std::string to_string(const SymbolTableType::type& val) { + std::map::const_iterator it = _SymbolTableType_VALUES_TO_NAMES.find(val); + if (it != _SymbolTableType_VALUES_TO_NAMES.end()) { + return std::string(it->second); + } else { + return std::to_string(static_cast(val)); + } +} + int _kBoundaryOrderValues[] = { BoundaryOrder::UNORDERED, BoundaryOrder::ASCENDING, @@ -3554,6 +3599,70 @@ void BloomFilterHeader::printTo(std::ostream& out) const { } +SymbolTablePageHeader::~SymbolTablePageHeader() noexcept { +} + +SymbolTablePageHeader::SymbolTablePageHeader() noexcept + : type(static_cast(0)), + is_compressed(0) { +} + +void SymbolTablePageHeader::__set_type(const SymbolTableType::type val) { + this->type = val; +} + +void SymbolTablePageHeader::__set_is_compressed(const bool val) { + this->is_compressed = val; +} +std::ostream& operator<<(std::ostream& out, const SymbolTablePageHeader& obj) +{ + obj.printTo(out); + return out; +} + + +void swap(SymbolTablePageHeader &a, SymbolTablePageHeader &b) noexcept { + using ::std::swap; + swap(a.type, b.type); + swap(a.is_compressed, b.is_compressed); +} + +bool SymbolTablePageHeader::operator==(const SymbolTablePageHeader & rhs) const +{ + if (!(type == rhs.type)) + return false; + if (!(is_compressed == rhs.is_compressed)) + return false; + return true; +} + +SymbolTablePageHeader::SymbolTablePageHeader(const SymbolTablePageHeader& other180) noexcept { + type = other180.type; + is_compressed = other180.is_compressed; +} +SymbolTablePageHeader::SymbolTablePageHeader(SymbolTablePageHeader&& other181) noexcept { + type = other181.type; + is_compressed = other181.is_compressed; +} +SymbolTablePageHeader& SymbolTablePageHeader::operator=(const SymbolTablePageHeader& other182) noexcept { + type = other182.type; + is_compressed = other182.is_compressed; + return *this; +} +SymbolTablePageHeader& SymbolTablePageHeader::operator=(SymbolTablePageHeader&& other183) noexcept { + type = other183.type; + is_compressed = other183.is_compressed; + return *this; +} +void SymbolTablePageHeader::printTo(std::ostream& out) const { + using ::apache::thrift::to_string; + out << "SymbolTablePageHeader("; + out << "type=" << to_string(type); + out << ", " << "is_compressed=" << to_string(is_compressed); + out << ")"; +} + + PageHeader::~PageHeader() noexcept { } @@ -3600,6 +3709,11 @@ void PageHeader::__set_data_page_header_v2(const DataPageHeaderV2& val) { this->data_page_header_v2 = val; __isset.data_page_header_v2 = true; } + +void PageHeader::__set_symbol_table_page_header(const SymbolTablePageHeader& val) { + this->symbol_table_page_header = val; +__isset.symbol_table_page_header = true; +} std::ostream& operator<<(std::ostream& out, const PageHeader& obj) { obj.printTo(out); @@ -3617,6 +3731,7 @@ void swap(PageHeader &a, PageHeader &b) noexcept { swap(a.index_page_header, b.index_page_header); swap(a.dictionary_page_header, b.dictionary_page_header); swap(a.data_page_header_v2, b.data_page_header_v2); + swap(a.symbol_table_page_header, b.symbol_table_page_header); swap(a.__isset, b.__isset); } @@ -3648,53 +3763,61 @@ bool PageHeader::operator==(const PageHeader & rhs) const return false; else if (__isset.data_page_header_v2 && !(data_page_header_v2 == rhs.data_page_header_v2)) return false; + if (__isset.symbol_table_page_header != rhs.__isset.symbol_table_page_header) + return false; + else if (__isset.symbol_table_page_header && !(symbol_table_page_header == rhs.symbol_table_page_header)) + return false; return true; } -PageHeader::PageHeader(const PageHeader& other180) { - type = other180.type; - uncompressed_page_size = other180.uncompressed_page_size; - compressed_page_size = other180.compressed_page_size; - crc = other180.crc; - data_page_header = other180.data_page_header; - index_page_header = other180.index_page_header; - dictionary_page_header = other180.dictionary_page_header; - data_page_header_v2 = other180.data_page_header_v2; - __isset = other180.__isset; -} -PageHeader::PageHeader(PageHeader&& other181) noexcept { - type = other181.type; - uncompressed_page_size = other181.uncompressed_page_size; - compressed_page_size = other181.compressed_page_size; - crc = other181.crc; - data_page_header = std::move(other181.data_page_header); - index_page_header = std::move(other181.index_page_header); - dictionary_page_header = std::move(other181.dictionary_page_header); - data_page_header_v2 = std::move(other181.data_page_header_v2); - __isset = other181.__isset; -} -PageHeader& PageHeader::operator=(const PageHeader& other182) { - type = other182.type; - uncompressed_page_size = other182.uncompressed_page_size; - compressed_page_size = other182.compressed_page_size; - crc = other182.crc; - data_page_header = other182.data_page_header; - index_page_header = other182.index_page_header; - dictionary_page_header = other182.dictionary_page_header; - data_page_header_v2 = other182.data_page_header_v2; - __isset = other182.__isset; +PageHeader::PageHeader(const PageHeader& other185) { + type = other185.type; + uncompressed_page_size = other185.uncompressed_page_size; + compressed_page_size = other185.compressed_page_size; + crc = other185.crc; + data_page_header = other185.data_page_header; + index_page_header = other185.index_page_header; + dictionary_page_header = other185.dictionary_page_header; + data_page_header_v2 = other185.data_page_header_v2; + symbol_table_page_header = other185.symbol_table_page_header; + __isset = other185.__isset; +} +PageHeader::PageHeader(PageHeader&& other186) noexcept { + type = other186.type; + uncompressed_page_size = other186.uncompressed_page_size; + compressed_page_size = other186.compressed_page_size; + crc = other186.crc; + data_page_header = std::move(other186.data_page_header); + index_page_header = std::move(other186.index_page_header); + dictionary_page_header = std::move(other186.dictionary_page_header); + data_page_header_v2 = std::move(other186.data_page_header_v2); + symbol_table_page_header = std::move(other186.symbol_table_page_header); + __isset = other186.__isset; +} +PageHeader& PageHeader::operator=(const PageHeader& other187) { + type = other187.type; + uncompressed_page_size = other187.uncompressed_page_size; + compressed_page_size = other187.compressed_page_size; + crc = other187.crc; + data_page_header = other187.data_page_header; + index_page_header = other187.index_page_header; + dictionary_page_header = other187.dictionary_page_header; + data_page_header_v2 = other187.data_page_header_v2; + symbol_table_page_header = other187.symbol_table_page_header; + __isset = other187.__isset; return *this; } -PageHeader& PageHeader::operator=(PageHeader&& other183) noexcept { - type = other183.type; - uncompressed_page_size = other183.uncompressed_page_size; - compressed_page_size = other183.compressed_page_size; - crc = other183.crc; - data_page_header = std::move(other183.data_page_header); - index_page_header = std::move(other183.index_page_header); - dictionary_page_header = std::move(other183.dictionary_page_header); - data_page_header_v2 = std::move(other183.data_page_header_v2); - __isset = other183.__isset; +PageHeader& PageHeader::operator=(PageHeader&& other188) noexcept { + type = other188.type; + uncompressed_page_size = other188.uncompressed_page_size; + compressed_page_size = other188.compressed_page_size; + crc = other188.crc; + data_page_header = std::move(other188.data_page_header); + index_page_header = std::move(other188.index_page_header); + dictionary_page_header = std::move(other188.dictionary_page_header); + data_page_header_v2 = std::move(other188.data_page_header_v2); + symbol_table_page_header = std::move(other188.symbol_table_page_header); + __isset = other188.__isset; return *this; } void PageHeader::printTo(std::ostream& out) const { @@ -3708,6 +3831,7 @@ void PageHeader::printTo(std::ostream& out) const { out << ", " << "index_page_header="; (__isset.index_page_header ? (out << to_string(index_page_header)) : (out << "")); out << ", " << "dictionary_page_header="; (__isset.dictionary_page_header ? (out << to_string(dictionary_page_header)) : (out << "")); out << ", " << "data_page_header_v2="; (__isset.data_page_header_v2 ? (out << to_string(data_page_header_v2)) : (out << "")); + out << ", " << "symbol_table_page_header="; (__isset.symbol_table_page_header ? (out << to_string(symbol_table_page_header)) : (out << "")); out << ")"; } @@ -3753,26 +3877,26 @@ bool KeyValue::operator==(const KeyValue & rhs) const return true; } -KeyValue::KeyValue(const KeyValue& other184) { - key = other184.key; - value = other184.value; - __isset = other184.__isset; +KeyValue::KeyValue(const KeyValue& other189) { + key = other189.key; + value = other189.value; + __isset = other189.__isset; } -KeyValue::KeyValue(KeyValue&& other185) noexcept { - key = std::move(other185.key); - value = std::move(other185.value); - __isset = other185.__isset; +KeyValue::KeyValue(KeyValue&& other190) noexcept { + key = std::move(other190.key); + value = std::move(other190.value); + __isset = other190.__isset; } -KeyValue& KeyValue::operator=(const KeyValue& other186) { - key = other186.key; - value = other186.value; - __isset = other186.__isset; +KeyValue& KeyValue::operator=(const KeyValue& other191) { + key = other191.key; + value = other191.value; + __isset = other191.__isset; return *this; } -KeyValue& KeyValue::operator=(KeyValue&& other187) noexcept { - key = std::move(other187.key); - value = std::move(other187.value); - __isset = other187.__isset; +KeyValue& KeyValue::operator=(KeyValue&& other192) noexcept { + key = std::move(other192.key); + value = std::move(other192.value); + __isset = other192.__isset; return *this; } void KeyValue::printTo(std::ostream& out) const { @@ -3829,26 +3953,26 @@ bool SortingColumn::operator==(const SortingColumn & rhs) const return true; } -SortingColumn::SortingColumn(const SortingColumn& other188) noexcept { - column_idx = other188.column_idx; - descending = other188.descending; - nulls_first = other188.nulls_first; +SortingColumn::SortingColumn(const SortingColumn& other193) noexcept { + column_idx = other193.column_idx; + descending = other193.descending; + nulls_first = other193.nulls_first; } -SortingColumn::SortingColumn(SortingColumn&& other189) noexcept { - column_idx = other189.column_idx; - descending = other189.descending; - nulls_first = other189.nulls_first; +SortingColumn::SortingColumn(SortingColumn&& other194) noexcept { + column_idx = other194.column_idx; + descending = other194.descending; + nulls_first = other194.nulls_first; } -SortingColumn& SortingColumn::operator=(const SortingColumn& other190) noexcept { - column_idx = other190.column_idx; - descending = other190.descending; - nulls_first = other190.nulls_first; +SortingColumn& SortingColumn::operator=(const SortingColumn& other195) noexcept { + column_idx = other195.column_idx; + descending = other195.descending; + nulls_first = other195.nulls_first; return *this; } -SortingColumn& SortingColumn::operator=(SortingColumn&& other191) noexcept { - column_idx = other191.column_idx; - descending = other191.descending; - nulls_first = other191.nulls_first; +SortingColumn& SortingColumn::operator=(SortingColumn&& other196) noexcept { + column_idx = other196.column_idx; + descending = other196.descending; + nulls_first = other196.nulls_first; return *this; } void SortingColumn::printTo(std::ostream& out) const { @@ -3906,26 +4030,26 @@ bool PageEncodingStats::operator==(const PageEncodingStats & rhs) const return true; } -PageEncodingStats::PageEncodingStats(const PageEncodingStats& other194) noexcept { - page_type = other194.page_type; - encoding = other194.encoding; - count = other194.count; +PageEncodingStats::PageEncodingStats(const PageEncodingStats& other199) noexcept { + page_type = other199.page_type; + encoding = other199.encoding; + count = other199.count; } -PageEncodingStats::PageEncodingStats(PageEncodingStats&& other195) noexcept { - page_type = other195.page_type; - encoding = other195.encoding; - count = other195.count; +PageEncodingStats::PageEncodingStats(PageEncodingStats&& other200) noexcept { + page_type = other200.page_type; + encoding = other200.encoding; + count = other200.count; } -PageEncodingStats& PageEncodingStats::operator=(const PageEncodingStats& other196) noexcept { - page_type = other196.page_type; - encoding = other196.encoding; - count = other196.count; +PageEncodingStats& PageEncodingStats::operator=(const PageEncodingStats& other201) noexcept { + page_type = other201.page_type; + encoding = other201.encoding; + count = other201.count; return *this; } -PageEncodingStats& PageEncodingStats::operator=(PageEncodingStats&& other197) noexcept { - page_type = other197.page_type; - encoding = other197.encoding; - count = other197.count; +PageEncodingStats& PageEncodingStats::operator=(PageEncodingStats&& other202) noexcept { + page_type = other202.page_type; + encoding = other202.encoding; + count = other202.count; return *this; } void PageEncodingStats::printTo(std::ostream& out) const { @@ -3951,7 +4075,9 @@ ColumnMetaData::ColumnMetaData() noexcept index_page_offset(0), dictionary_page_offset(0), bloom_filter_offset(0), - bloom_filter_length(0) { + bloom_filter_length(0), + symbol_table_page_offset(0), + symbol_table_page_length(0) { } void ColumnMetaData::__set_type(const Type::type val) { @@ -4030,6 +4156,16 @@ void ColumnMetaData::__set_geospatial_statistics(const GeospatialStatistics& val this->geospatial_statistics = val; __isset.geospatial_statistics = true; } + +void ColumnMetaData::__set_symbol_table_page_offset(const int64_t val) { + this->symbol_table_page_offset = val; +__isset.symbol_table_page_offset = true; +} + +void ColumnMetaData::__set_symbol_table_page_length(const int32_t val) { + this->symbol_table_page_length = val; +__isset.symbol_table_page_length = true; +} std::ostream& operator<<(std::ostream& out, const ColumnMetaData& obj) { obj.printTo(out); @@ -4056,6 +4192,8 @@ void swap(ColumnMetaData &a, ColumnMetaData &b) noexcept { swap(a.bloom_filter_length, b.bloom_filter_length); swap(a.size_statistics, b.size_statistics); swap(a.geospatial_statistics, b.geospatial_statistics); + swap(a.symbol_table_page_offset, b.symbol_table_page_offset); + swap(a.symbol_table_page_length, b.symbol_table_page_length); swap(a.__isset, b.__isset); } @@ -4113,89 +4251,105 @@ bool ColumnMetaData::operator==(const ColumnMetaData & rhs) const return false; else if (__isset.geospatial_statistics && !(geospatial_statistics == rhs.geospatial_statistics)) return false; + if (__isset.symbol_table_page_offset != rhs.__isset.symbol_table_page_offset) + return false; + else if (__isset.symbol_table_page_offset && !(symbol_table_page_offset == rhs.symbol_table_page_offset)) + return false; + if (__isset.symbol_table_page_length != rhs.__isset.symbol_table_page_length) + return false; + else if (__isset.symbol_table_page_length && !(symbol_table_page_length == rhs.symbol_table_page_length)) + return false; return true; } -ColumnMetaData::ColumnMetaData(const ColumnMetaData& other225) { - type = other225.type; - encodings = other225.encodings; - path_in_schema = other225.path_in_schema; - codec = other225.codec; - num_values = other225.num_values; - total_uncompressed_size = other225.total_uncompressed_size; - total_compressed_size = other225.total_compressed_size; - key_value_metadata = other225.key_value_metadata; - data_page_offset = other225.data_page_offset; - index_page_offset = other225.index_page_offset; - dictionary_page_offset = other225.dictionary_page_offset; - statistics = other225.statistics; - encoding_stats = other225.encoding_stats; - bloom_filter_offset = other225.bloom_filter_offset; - bloom_filter_length = other225.bloom_filter_length; - size_statistics = other225.size_statistics; - geospatial_statistics = other225.geospatial_statistics; - __isset = other225.__isset; -} -ColumnMetaData::ColumnMetaData(ColumnMetaData&& other226) noexcept { - type = other226.type; - encodings = std::move(other226.encodings); - path_in_schema = std::move(other226.path_in_schema); - codec = other226.codec; - num_values = other226.num_values; - total_uncompressed_size = other226.total_uncompressed_size; - total_compressed_size = other226.total_compressed_size; - key_value_metadata = std::move(other226.key_value_metadata); - data_page_offset = other226.data_page_offset; - index_page_offset = other226.index_page_offset; - dictionary_page_offset = other226.dictionary_page_offset; - statistics = std::move(other226.statistics); - encoding_stats = std::move(other226.encoding_stats); - bloom_filter_offset = other226.bloom_filter_offset; - bloom_filter_length = other226.bloom_filter_length; - size_statistics = std::move(other226.size_statistics); - geospatial_statistics = std::move(other226.geospatial_statistics); - __isset = other226.__isset; -} -ColumnMetaData& ColumnMetaData::operator=(const ColumnMetaData& other227) { - type = other227.type; - encodings = other227.encodings; - path_in_schema = other227.path_in_schema; - codec = other227.codec; - num_values = other227.num_values; - total_uncompressed_size = other227.total_uncompressed_size; - total_compressed_size = other227.total_compressed_size; - key_value_metadata = other227.key_value_metadata; - data_page_offset = other227.data_page_offset; - index_page_offset = other227.index_page_offset; - dictionary_page_offset = other227.dictionary_page_offset; - statistics = other227.statistics; - encoding_stats = other227.encoding_stats; - bloom_filter_offset = other227.bloom_filter_offset; - bloom_filter_length = other227.bloom_filter_length; - size_statistics = other227.size_statistics; - geospatial_statistics = other227.geospatial_statistics; - __isset = other227.__isset; +ColumnMetaData::ColumnMetaData(const ColumnMetaData& other230) { + type = other230.type; + encodings = other230.encodings; + path_in_schema = other230.path_in_schema; + codec = other230.codec; + num_values = other230.num_values; + total_uncompressed_size = other230.total_uncompressed_size; + total_compressed_size = other230.total_compressed_size; + key_value_metadata = other230.key_value_metadata; + data_page_offset = other230.data_page_offset; + index_page_offset = other230.index_page_offset; + dictionary_page_offset = other230.dictionary_page_offset; + statistics = other230.statistics; + encoding_stats = other230.encoding_stats; + bloom_filter_offset = other230.bloom_filter_offset; + bloom_filter_length = other230.bloom_filter_length; + size_statistics = other230.size_statistics; + geospatial_statistics = other230.geospatial_statistics; + symbol_table_page_offset = other230.symbol_table_page_offset; + symbol_table_page_length = other230.symbol_table_page_length; + __isset = other230.__isset; +} +ColumnMetaData::ColumnMetaData(ColumnMetaData&& other231) noexcept { + type = other231.type; + encodings = std::move(other231.encodings); + path_in_schema = std::move(other231.path_in_schema); + codec = other231.codec; + num_values = other231.num_values; + total_uncompressed_size = other231.total_uncompressed_size; + total_compressed_size = other231.total_compressed_size; + key_value_metadata = std::move(other231.key_value_metadata); + data_page_offset = other231.data_page_offset; + index_page_offset = other231.index_page_offset; + dictionary_page_offset = other231.dictionary_page_offset; + statistics = std::move(other231.statistics); + encoding_stats = std::move(other231.encoding_stats); + bloom_filter_offset = other231.bloom_filter_offset; + bloom_filter_length = other231.bloom_filter_length; + size_statistics = std::move(other231.size_statistics); + geospatial_statistics = std::move(other231.geospatial_statistics); + symbol_table_page_offset = other231.symbol_table_page_offset; + symbol_table_page_length = other231.symbol_table_page_length; + __isset = other231.__isset; +} +ColumnMetaData& ColumnMetaData::operator=(const ColumnMetaData& other232) { + type = other232.type; + encodings = other232.encodings; + path_in_schema = other232.path_in_schema; + codec = other232.codec; + num_values = other232.num_values; + total_uncompressed_size = other232.total_uncompressed_size; + total_compressed_size = other232.total_compressed_size; + key_value_metadata = other232.key_value_metadata; + data_page_offset = other232.data_page_offset; + index_page_offset = other232.index_page_offset; + dictionary_page_offset = other232.dictionary_page_offset; + statistics = other232.statistics; + encoding_stats = other232.encoding_stats; + bloom_filter_offset = other232.bloom_filter_offset; + bloom_filter_length = other232.bloom_filter_length; + size_statistics = other232.size_statistics; + geospatial_statistics = other232.geospatial_statistics; + symbol_table_page_offset = other232.symbol_table_page_offset; + symbol_table_page_length = other232.symbol_table_page_length; + __isset = other232.__isset; return *this; } -ColumnMetaData& ColumnMetaData::operator=(ColumnMetaData&& other228) noexcept { - type = other228.type; - encodings = std::move(other228.encodings); - path_in_schema = std::move(other228.path_in_schema); - codec = other228.codec; - num_values = other228.num_values; - total_uncompressed_size = other228.total_uncompressed_size; - total_compressed_size = other228.total_compressed_size; - key_value_metadata = std::move(other228.key_value_metadata); - data_page_offset = other228.data_page_offset; - index_page_offset = other228.index_page_offset; - dictionary_page_offset = other228.dictionary_page_offset; - statistics = std::move(other228.statistics); - encoding_stats = std::move(other228.encoding_stats); - bloom_filter_offset = other228.bloom_filter_offset; - bloom_filter_length = other228.bloom_filter_length; - size_statistics = std::move(other228.size_statistics); - geospatial_statistics = std::move(other228.geospatial_statistics); - __isset = other228.__isset; +ColumnMetaData& ColumnMetaData::operator=(ColumnMetaData&& other233) noexcept { + type = other233.type; + encodings = std::move(other233.encodings); + path_in_schema = std::move(other233.path_in_schema); + codec = other233.codec; + num_values = other233.num_values; + total_uncompressed_size = other233.total_uncompressed_size; + total_compressed_size = other233.total_compressed_size; + key_value_metadata = std::move(other233.key_value_metadata); + data_page_offset = other233.data_page_offset; + index_page_offset = other233.index_page_offset; + dictionary_page_offset = other233.dictionary_page_offset; + statistics = std::move(other233.statistics); + encoding_stats = std::move(other233.encoding_stats); + bloom_filter_offset = other233.bloom_filter_offset; + bloom_filter_length = other233.bloom_filter_length; + size_statistics = std::move(other233.size_statistics); + geospatial_statistics = std::move(other233.geospatial_statistics); + symbol_table_page_offset = other233.symbol_table_page_offset; + symbol_table_page_length = other233.symbol_table_page_length; + __isset = other233.__isset; return *this; } void ColumnMetaData::printTo(std::ostream& out) const { @@ -4218,6 +4372,8 @@ void ColumnMetaData::printTo(std::ostream& out) const { out << ", " << "bloom_filter_length="; (__isset.bloom_filter_length ? (out << to_string(bloom_filter_length)) : (out << "")); out << ", " << "size_statistics="; (__isset.size_statistics ? (out << to_string(size_statistics)) : (out << "")); out << ", " << "geospatial_statistics="; (__isset.geospatial_statistics ? (out << to_string(geospatial_statistics)) : (out << "")); + out << ", " << "symbol_table_page_offset="; (__isset.symbol_table_page_offset ? (out << to_string(symbol_table_page_offset)) : (out << "")); + out << ", " << "symbol_table_page_length="; (__isset.symbol_table_page_length ? (out << to_string(symbol_table_page_length)) : (out << "")); out << ")"; } @@ -4245,18 +4401,18 @@ bool EncryptionWithFooterKey::operator==(const EncryptionWithFooterKey & /* rhs return true; } -EncryptionWithFooterKey::EncryptionWithFooterKey(const EncryptionWithFooterKey& other229) noexcept { - (void) other229; +EncryptionWithFooterKey::EncryptionWithFooterKey(const EncryptionWithFooterKey& other234) noexcept { + (void) other234; } -EncryptionWithFooterKey::EncryptionWithFooterKey(EncryptionWithFooterKey&& other230) noexcept { - (void) other230; +EncryptionWithFooterKey::EncryptionWithFooterKey(EncryptionWithFooterKey&& other235) noexcept { + (void) other235; } -EncryptionWithFooterKey& EncryptionWithFooterKey::operator=(const EncryptionWithFooterKey& other231) noexcept { - (void) other231; +EncryptionWithFooterKey& EncryptionWithFooterKey::operator=(const EncryptionWithFooterKey& other236) noexcept { + (void) other236; return *this; } -EncryptionWithFooterKey& EncryptionWithFooterKey::operator=(EncryptionWithFooterKey&& other232) noexcept { - (void) other232; +EncryptionWithFooterKey& EncryptionWithFooterKey::operator=(EncryptionWithFooterKey&& other237) noexcept { + (void) other237; return *this; } void EncryptionWithFooterKey::printTo(std::ostream& out) const { @@ -4306,26 +4462,26 @@ bool EncryptionWithColumnKey::operator==(const EncryptionWithColumnKey & rhs) co return true; } -EncryptionWithColumnKey::EncryptionWithColumnKey(const EncryptionWithColumnKey& other239) { - path_in_schema = other239.path_in_schema; - key_metadata = other239.key_metadata; - __isset = other239.__isset; +EncryptionWithColumnKey::EncryptionWithColumnKey(const EncryptionWithColumnKey& other244) { + path_in_schema = other244.path_in_schema; + key_metadata = other244.key_metadata; + __isset = other244.__isset; } -EncryptionWithColumnKey::EncryptionWithColumnKey(EncryptionWithColumnKey&& other240) noexcept { - path_in_schema = std::move(other240.path_in_schema); - key_metadata = std::move(other240.key_metadata); - __isset = other240.__isset; +EncryptionWithColumnKey::EncryptionWithColumnKey(EncryptionWithColumnKey&& other245) noexcept { + path_in_schema = std::move(other245.path_in_schema); + key_metadata = std::move(other245.key_metadata); + __isset = other245.__isset; } -EncryptionWithColumnKey& EncryptionWithColumnKey::operator=(const EncryptionWithColumnKey& other241) { - path_in_schema = other241.path_in_schema; - key_metadata = other241.key_metadata; - __isset = other241.__isset; +EncryptionWithColumnKey& EncryptionWithColumnKey::operator=(const EncryptionWithColumnKey& other246) { + path_in_schema = other246.path_in_schema; + key_metadata = other246.key_metadata; + __isset = other246.__isset; return *this; } -EncryptionWithColumnKey& EncryptionWithColumnKey::operator=(EncryptionWithColumnKey&& other242) noexcept { - path_in_schema = std::move(other242.path_in_schema); - key_metadata = std::move(other242.key_metadata); - __isset = other242.__isset; +EncryptionWithColumnKey& EncryptionWithColumnKey::operator=(EncryptionWithColumnKey&& other247) noexcept { + path_in_schema = std::move(other247.path_in_schema); + key_metadata = std::move(other247.key_metadata); + __isset = other247.__isset; return *this; } void EncryptionWithColumnKey::printTo(std::ostream& out) const { @@ -4379,26 +4535,26 @@ bool ColumnCryptoMetaData::operator==(const ColumnCryptoMetaData & rhs) const return true; } -ColumnCryptoMetaData::ColumnCryptoMetaData(const ColumnCryptoMetaData& other243) { - ENCRYPTION_WITH_FOOTER_KEY = other243.ENCRYPTION_WITH_FOOTER_KEY; - ENCRYPTION_WITH_COLUMN_KEY = other243.ENCRYPTION_WITH_COLUMN_KEY; - __isset = other243.__isset; +ColumnCryptoMetaData::ColumnCryptoMetaData(const ColumnCryptoMetaData& other248) { + ENCRYPTION_WITH_FOOTER_KEY = other248.ENCRYPTION_WITH_FOOTER_KEY; + ENCRYPTION_WITH_COLUMN_KEY = other248.ENCRYPTION_WITH_COLUMN_KEY; + __isset = other248.__isset; } -ColumnCryptoMetaData::ColumnCryptoMetaData(ColumnCryptoMetaData&& other244) noexcept { - ENCRYPTION_WITH_FOOTER_KEY = std::move(other244.ENCRYPTION_WITH_FOOTER_KEY); - ENCRYPTION_WITH_COLUMN_KEY = std::move(other244.ENCRYPTION_WITH_COLUMN_KEY); - __isset = other244.__isset; +ColumnCryptoMetaData::ColumnCryptoMetaData(ColumnCryptoMetaData&& other249) noexcept { + ENCRYPTION_WITH_FOOTER_KEY = std::move(other249.ENCRYPTION_WITH_FOOTER_KEY); + ENCRYPTION_WITH_COLUMN_KEY = std::move(other249.ENCRYPTION_WITH_COLUMN_KEY); + __isset = other249.__isset; } -ColumnCryptoMetaData& ColumnCryptoMetaData::operator=(const ColumnCryptoMetaData& other245) { - ENCRYPTION_WITH_FOOTER_KEY = other245.ENCRYPTION_WITH_FOOTER_KEY; - ENCRYPTION_WITH_COLUMN_KEY = other245.ENCRYPTION_WITH_COLUMN_KEY; - __isset = other245.__isset; +ColumnCryptoMetaData& ColumnCryptoMetaData::operator=(const ColumnCryptoMetaData& other250) { + ENCRYPTION_WITH_FOOTER_KEY = other250.ENCRYPTION_WITH_FOOTER_KEY; + ENCRYPTION_WITH_COLUMN_KEY = other250.ENCRYPTION_WITH_COLUMN_KEY; + __isset = other250.__isset; return *this; } -ColumnCryptoMetaData& ColumnCryptoMetaData::operator=(ColumnCryptoMetaData&& other246) noexcept { - ENCRYPTION_WITH_FOOTER_KEY = std::move(other246.ENCRYPTION_WITH_FOOTER_KEY); - ENCRYPTION_WITH_COLUMN_KEY = std::move(other246.ENCRYPTION_WITH_COLUMN_KEY); - __isset = other246.__isset; +ColumnCryptoMetaData& ColumnCryptoMetaData::operator=(ColumnCryptoMetaData&& other251) noexcept { + ENCRYPTION_WITH_FOOTER_KEY = std::move(other251.ENCRYPTION_WITH_FOOTER_KEY); + ENCRYPTION_WITH_COLUMN_KEY = std::move(other251.ENCRYPTION_WITH_COLUMN_KEY); + __isset = other251.__isset; return *this; } void ColumnCryptoMetaData::printTo(std::ostream& out) const { @@ -4526,54 +4682,54 @@ bool ColumnChunk::operator==(const ColumnChunk & rhs) const return true; } -ColumnChunk::ColumnChunk(const ColumnChunk& other247) { - file_path = other247.file_path; - file_offset = other247.file_offset; - meta_data = other247.meta_data; - offset_index_offset = other247.offset_index_offset; - offset_index_length = other247.offset_index_length; - column_index_offset = other247.column_index_offset; - column_index_length = other247.column_index_length; - crypto_metadata = other247.crypto_metadata; - encrypted_column_metadata = other247.encrypted_column_metadata; - __isset = other247.__isset; -} -ColumnChunk::ColumnChunk(ColumnChunk&& other248) noexcept { - file_path = std::move(other248.file_path); - file_offset = other248.file_offset; - meta_data = std::move(other248.meta_data); - offset_index_offset = other248.offset_index_offset; - offset_index_length = other248.offset_index_length; - column_index_offset = other248.column_index_offset; - column_index_length = other248.column_index_length; - crypto_metadata = std::move(other248.crypto_metadata); - encrypted_column_metadata = std::move(other248.encrypted_column_metadata); - __isset = other248.__isset; -} -ColumnChunk& ColumnChunk::operator=(const ColumnChunk& other249) { - file_path = other249.file_path; - file_offset = other249.file_offset; - meta_data = other249.meta_data; - offset_index_offset = other249.offset_index_offset; - offset_index_length = other249.offset_index_length; - column_index_offset = other249.column_index_offset; - column_index_length = other249.column_index_length; - crypto_metadata = other249.crypto_metadata; - encrypted_column_metadata = other249.encrypted_column_metadata; - __isset = other249.__isset; +ColumnChunk::ColumnChunk(const ColumnChunk& other252) { + file_path = other252.file_path; + file_offset = other252.file_offset; + meta_data = other252.meta_data; + offset_index_offset = other252.offset_index_offset; + offset_index_length = other252.offset_index_length; + column_index_offset = other252.column_index_offset; + column_index_length = other252.column_index_length; + crypto_metadata = other252.crypto_metadata; + encrypted_column_metadata = other252.encrypted_column_metadata; + __isset = other252.__isset; +} +ColumnChunk::ColumnChunk(ColumnChunk&& other253) noexcept { + file_path = std::move(other253.file_path); + file_offset = other253.file_offset; + meta_data = std::move(other253.meta_data); + offset_index_offset = other253.offset_index_offset; + offset_index_length = other253.offset_index_length; + column_index_offset = other253.column_index_offset; + column_index_length = other253.column_index_length; + crypto_metadata = std::move(other253.crypto_metadata); + encrypted_column_metadata = std::move(other253.encrypted_column_metadata); + __isset = other253.__isset; +} +ColumnChunk& ColumnChunk::operator=(const ColumnChunk& other254) { + file_path = other254.file_path; + file_offset = other254.file_offset; + meta_data = other254.meta_data; + offset_index_offset = other254.offset_index_offset; + offset_index_length = other254.offset_index_length; + column_index_offset = other254.column_index_offset; + column_index_length = other254.column_index_length; + crypto_metadata = other254.crypto_metadata; + encrypted_column_metadata = other254.encrypted_column_metadata; + __isset = other254.__isset; return *this; } -ColumnChunk& ColumnChunk::operator=(ColumnChunk&& other250) noexcept { - file_path = std::move(other250.file_path); - file_offset = other250.file_offset; - meta_data = std::move(other250.meta_data); - offset_index_offset = other250.offset_index_offset; - offset_index_length = other250.offset_index_length; - column_index_offset = other250.column_index_offset; - column_index_length = other250.column_index_length; - crypto_metadata = std::move(other250.crypto_metadata); - encrypted_column_metadata = std::move(other250.encrypted_column_metadata); - __isset = other250.__isset; +ColumnChunk& ColumnChunk::operator=(ColumnChunk&& other255) noexcept { + file_path = std::move(other255.file_path); + file_offset = other255.file_offset; + meta_data = std::move(other255.meta_data); + offset_index_offset = other255.offset_index_offset; + offset_index_length = other255.offset_index_length; + column_index_offset = other255.column_index_offset; + column_index_length = other255.column_index_length; + crypto_metadata = std::move(other255.crypto_metadata); + encrypted_column_metadata = std::move(other255.encrypted_column_metadata); + __isset = other255.__isset; return *this; } void ColumnChunk::printTo(std::ostream& out) const { @@ -4680,46 +4836,46 @@ bool RowGroup::operator==(const RowGroup & rhs) const return true; } -RowGroup::RowGroup(const RowGroup& other263) { - columns = other263.columns; - total_byte_size = other263.total_byte_size; - num_rows = other263.num_rows; - sorting_columns = other263.sorting_columns; - file_offset = other263.file_offset; - total_compressed_size = other263.total_compressed_size; - ordinal = other263.ordinal; - __isset = other263.__isset; -} -RowGroup::RowGroup(RowGroup&& other264) noexcept { - columns = std::move(other264.columns); - total_byte_size = other264.total_byte_size; - num_rows = other264.num_rows; - sorting_columns = std::move(other264.sorting_columns); - file_offset = other264.file_offset; - total_compressed_size = other264.total_compressed_size; - ordinal = other264.ordinal; - __isset = other264.__isset; -} -RowGroup& RowGroup::operator=(const RowGroup& other265) { - columns = other265.columns; - total_byte_size = other265.total_byte_size; - num_rows = other265.num_rows; - sorting_columns = other265.sorting_columns; - file_offset = other265.file_offset; - total_compressed_size = other265.total_compressed_size; - ordinal = other265.ordinal; - __isset = other265.__isset; +RowGroup::RowGroup(const RowGroup& other268) { + columns = other268.columns; + total_byte_size = other268.total_byte_size; + num_rows = other268.num_rows; + sorting_columns = other268.sorting_columns; + file_offset = other268.file_offset; + total_compressed_size = other268.total_compressed_size; + ordinal = other268.ordinal; + __isset = other268.__isset; +} +RowGroup::RowGroup(RowGroup&& other269) noexcept { + columns = std::move(other269.columns); + total_byte_size = other269.total_byte_size; + num_rows = other269.num_rows; + sorting_columns = std::move(other269.sorting_columns); + file_offset = other269.file_offset; + total_compressed_size = other269.total_compressed_size; + ordinal = other269.ordinal; + __isset = other269.__isset; +} +RowGroup& RowGroup::operator=(const RowGroup& other270) { + columns = other270.columns; + total_byte_size = other270.total_byte_size; + num_rows = other270.num_rows; + sorting_columns = other270.sorting_columns; + file_offset = other270.file_offset; + total_compressed_size = other270.total_compressed_size; + ordinal = other270.ordinal; + __isset = other270.__isset; return *this; } -RowGroup& RowGroup::operator=(RowGroup&& other266) noexcept { - columns = std::move(other266.columns); - total_byte_size = other266.total_byte_size; - num_rows = other266.num_rows; - sorting_columns = std::move(other266.sorting_columns); - file_offset = other266.file_offset; - total_compressed_size = other266.total_compressed_size; - ordinal = other266.ordinal; - __isset = other266.__isset; +RowGroup& RowGroup::operator=(RowGroup&& other271) noexcept { + columns = std::move(other271.columns); + total_byte_size = other271.total_byte_size; + num_rows = other271.num_rows; + sorting_columns = std::move(other271.sorting_columns); + file_offset = other271.file_offset; + total_compressed_size = other271.total_compressed_size; + ordinal = other271.ordinal; + __isset = other271.__isset; return *this; } void RowGroup::printTo(std::ostream& out) const { @@ -4759,18 +4915,18 @@ bool TypeDefinedOrder::operator==(const TypeDefinedOrder & /* rhs */) const return true; } -TypeDefinedOrder::TypeDefinedOrder(const TypeDefinedOrder& other267) noexcept { - (void) other267; +TypeDefinedOrder::TypeDefinedOrder(const TypeDefinedOrder& other272) noexcept { + (void) other272; } -TypeDefinedOrder::TypeDefinedOrder(TypeDefinedOrder&& other268) noexcept { - (void) other268; +TypeDefinedOrder::TypeDefinedOrder(TypeDefinedOrder&& other273) noexcept { + (void) other273; } -TypeDefinedOrder& TypeDefinedOrder::operator=(const TypeDefinedOrder& other269) noexcept { - (void) other269; +TypeDefinedOrder& TypeDefinedOrder::operator=(const TypeDefinedOrder& other274) noexcept { + (void) other274; return *this; } -TypeDefinedOrder& TypeDefinedOrder::operator=(TypeDefinedOrder&& other270) noexcept { - (void) other270; +TypeDefinedOrder& TypeDefinedOrder::operator=(TypeDefinedOrder&& other275) noexcept { + (void) other275; return *this; } void TypeDefinedOrder::printTo(std::ostream& out) const { @@ -4803,18 +4959,18 @@ bool IEEE754TotalOrder::operator==(const IEEE754TotalOrder & /* rhs */) const return true; } -IEEE754TotalOrder::IEEE754TotalOrder(const IEEE754TotalOrder& other271) noexcept { - (void) other271; +IEEE754TotalOrder::IEEE754TotalOrder(const IEEE754TotalOrder& other276) noexcept { + (void) other276; } -IEEE754TotalOrder::IEEE754TotalOrder(IEEE754TotalOrder&& other272) noexcept { - (void) other272; +IEEE754TotalOrder::IEEE754TotalOrder(IEEE754TotalOrder&& other277) noexcept { + (void) other277; } -IEEE754TotalOrder& IEEE754TotalOrder::operator=(const IEEE754TotalOrder& other273) noexcept { - (void) other273; +IEEE754TotalOrder& IEEE754TotalOrder::operator=(const IEEE754TotalOrder& other278) noexcept { + (void) other278; return *this; } -IEEE754TotalOrder& IEEE754TotalOrder::operator=(IEEE754TotalOrder&& other274) noexcept { - (void) other274; +IEEE754TotalOrder& IEEE754TotalOrder::operator=(IEEE754TotalOrder&& other279) noexcept { + (void) other279; return *this; } void IEEE754TotalOrder::printTo(std::ostream& out) const { @@ -4866,26 +5022,26 @@ bool ColumnOrder::operator==(const ColumnOrder & rhs) const return true; } -ColumnOrder::ColumnOrder(const ColumnOrder& other275) noexcept { - TYPE_ORDER = other275.TYPE_ORDER; - IEEE_754_TOTAL_ORDER = other275.IEEE_754_TOTAL_ORDER; - __isset = other275.__isset; +ColumnOrder::ColumnOrder(const ColumnOrder& other280) noexcept { + TYPE_ORDER = other280.TYPE_ORDER; + IEEE_754_TOTAL_ORDER = other280.IEEE_754_TOTAL_ORDER; + __isset = other280.__isset; } -ColumnOrder::ColumnOrder(ColumnOrder&& other276) noexcept { - TYPE_ORDER = std::move(other276.TYPE_ORDER); - IEEE_754_TOTAL_ORDER = std::move(other276.IEEE_754_TOTAL_ORDER); - __isset = other276.__isset; +ColumnOrder::ColumnOrder(ColumnOrder&& other281) noexcept { + TYPE_ORDER = std::move(other281.TYPE_ORDER); + IEEE_754_TOTAL_ORDER = std::move(other281.IEEE_754_TOTAL_ORDER); + __isset = other281.__isset; } -ColumnOrder& ColumnOrder::operator=(const ColumnOrder& other277) noexcept { - TYPE_ORDER = other277.TYPE_ORDER; - IEEE_754_TOTAL_ORDER = other277.IEEE_754_TOTAL_ORDER; - __isset = other277.__isset; +ColumnOrder& ColumnOrder::operator=(const ColumnOrder& other282) noexcept { + TYPE_ORDER = other282.TYPE_ORDER; + IEEE_754_TOTAL_ORDER = other282.IEEE_754_TOTAL_ORDER; + __isset = other282.__isset; return *this; } -ColumnOrder& ColumnOrder::operator=(ColumnOrder&& other278) noexcept { - TYPE_ORDER = std::move(other278.TYPE_ORDER); - IEEE_754_TOTAL_ORDER = std::move(other278.IEEE_754_TOTAL_ORDER); - __isset = other278.__isset; +ColumnOrder& ColumnOrder::operator=(ColumnOrder&& other283) noexcept { + TYPE_ORDER = std::move(other283.TYPE_ORDER); + IEEE_754_TOTAL_ORDER = std::move(other283.IEEE_754_TOTAL_ORDER); + __isset = other283.__isset; return *this; } void ColumnOrder::printTo(std::ostream& out) const { @@ -4942,26 +5098,26 @@ bool PageLocation::operator==(const PageLocation & rhs) const return true; } -PageLocation::PageLocation(const PageLocation& other279) noexcept { - offset = other279.offset; - compressed_page_size = other279.compressed_page_size; - first_row_index = other279.first_row_index; +PageLocation::PageLocation(const PageLocation& other284) noexcept { + offset = other284.offset; + compressed_page_size = other284.compressed_page_size; + first_row_index = other284.first_row_index; } -PageLocation::PageLocation(PageLocation&& other280) noexcept { - offset = other280.offset; - compressed_page_size = other280.compressed_page_size; - first_row_index = other280.first_row_index; +PageLocation::PageLocation(PageLocation&& other285) noexcept { + offset = other285.offset; + compressed_page_size = other285.compressed_page_size; + first_row_index = other285.first_row_index; } -PageLocation& PageLocation::operator=(const PageLocation& other281) noexcept { - offset = other281.offset; - compressed_page_size = other281.compressed_page_size; - first_row_index = other281.first_row_index; +PageLocation& PageLocation::operator=(const PageLocation& other286) noexcept { + offset = other286.offset; + compressed_page_size = other286.compressed_page_size; + first_row_index = other286.first_row_index; return *this; } -PageLocation& PageLocation::operator=(PageLocation&& other282) noexcept { - offset = other282.offset; - compressed_page_size = other282.compressed_page_size; - first_row_index = other282.first_row_index; +PageLocation& PageLocation::operator=(PageLocation&& other287) noexcept { + offset = other287.offset; + compressed_page_size = other287.compressed_page_size; + first_row_index = other287.first_row_index; return *this; } void PageLocation::printTo(std::ostream& out) const { @@ -5013,26 +5169,26 @@ bool OffsetIndex::operator==(const OffsetIndex & rhs) const return true; } -OffsetIndex::OffsetIndex(const OffsetIndex& other295) { - page_locations = other295.page_locations; - unencoded_byte_array_data_bytes = other295.unencoded_byte_array_data_bytes; - __isset = other295.__isset; +OffsetIndex::OffsetIndex(const OffsetIndex& other300) { + page_locations = other300.page_locations; + unencoded_byte_array_data_bytes = other300.unencoded_byte_array_data_bytes; + __isset = other300.__isset; } -OffsetIndex::OffsetIndex(OffsetIndex&& other296) noexcept { - page_locations = std::move(other296.page_locations); - unencoded_byte_array_data_bytes = std::move(other296.unencoded_byte_array_data_bytes); - __isset = other296.__isset; +OffsetIndex::OffsetIndex(OffsetIndex&& other301) noexcept { + page_locations = std::move(other301.page_locations); + unencoded_byte_array_data_bytes = std::move(other301.unencoded_byte_array_data_bytes); + __isset = other301.__isset; } -OffsetIndex& OffsetIndex::operator=(const OffsetIndex& other297) { - page_locations = other297.page_locations; - unencoded_byte_array_data_bytes = other297.unencoded_byte_array_data_bytes; - __isset = other297.__isset; +OffsetIndex& OffsetIndex::operator=(const OffsetIndex& other302) { + page_locations = other302.page_locations; + unencoded_byte_array_data_bytes = other302.unencoded_byte_array_data_bytes; + __isset = other302.__isset; return *this; } -OffsetIndex& OffsetIndex::operator=(OffsetIndex&& other298) noexcept { - page_locations = std::move(other298.page_locations); - unencoded_byte_array_data_bytes = std::move(other298.unencoded_byte_array_data_bytes); - __isset = other298.__isset; +OffsetIndex& OffsetIndex::operator=(OffsetIndex&& other303) noexcept { + page_locations = std::move(other303.page_locations); + unencoded_byte_array_data_bytes = std::move(other303.unencoded_byte_array_data_bytes); + __isset = other303.__isset; return *this; } void OffsetIndex::printTo(std::ostream& out) const { @@ -5135,50 +5291,50 @@ bool ColumnIndex::operator==(const ColumnIndex & rhs) const return true; } -ColumnIndex::ColumnIndex(const ColumnIndex& other342) { - null_pages = other342.null_pages; - min_values = other342.min_values; - max_values = other342.max_values; - boundary_order = other342.boundary_order; - null_counts = other342.null_counts; - repetition_level_histograms = other342.repetition_level_histograms; - definition_level_histograms = other342.definition_level_histograms; - nan_counts = other342.nan_counts; - __isset = other342.__isset; -} -ColumnIndex::ColumnIndex(ColumnIndex&& other343) noexcept { - null_pages = std::move(other343.null_pages); - min_values = std::move(other343.min_values); - max_values = std::move(other343.max_values); - boundary_order = other343.boundary_order; - null_counts = std::move(other343.null_counts); - repetition_level_histograms = std::move(other343.repetition_level_histograms); - definition_level_histograms = std::move(other343.definition_level_histograms); - nan_counts = std::move(other343.nan_counts); - __isset = other343.__isset; -} -ColumnIndex& ColumnIndex::operator=(const ColumnIndex& other344) { - null_pages = other344.null_pages; - min_values = other344.min_values; - max_values = other344.max_values; - boundary_order = other344.boundary_order; - null_counts = other344.null_counts; - repetition_level_histograms = other344.repetition_level_histograms; - definition_level_histograms = other344.definition_level_histograms; - nan_counts = other344.nan_counts; - __isset = other344.__isset; +ColumnIndex::ColumnIndex(const ColumnIndex& other347) { + null_pages = other347.null_pages; + min_values = other347.min_values; + max_values = other347.max_values; + boundary_order = other347.boundary_order; + null_counts = other347.null_counts; + repetition_level_histograms = other347.repetition_level_histograms; + definition_level_histograms = other347.definition_level_histograms; + nan_counts = other347.nan_counts; + __isset = other347.__isset; +} +ColumnIndex::ColumnIndex(ColumnIndex&& other348) noexcept { + null_pages = std::move(other348.null_pages); + min_values = std::move(other348.min_values); + max_values = std::move(other348.max_values); + boundary_order = other348.boundary_order; + null_counts = std::move(other348.null_counts); + repetition_level_histograms = std::move(other348.repetition_level_histograms); + definition_level_histograms = std::move(other348.definition_level_histograms); + nan_counts = std::move(other348.nan_counts); + __isset = other348.__isset; +} +ColumnIndex& ColumnIndex::operator=(const ColumnIndex& other349) { + null_pages = other349.null_pages; + min_values = other349.min_values; + max_values = other349.max_values; + boundary_order = other349.boundary_order; + null_counts = other349.null_counts; + repetition_level_histograms = other349.repetition_level_histograms; + definition_level_histograms = other349.definition_level_histograms; + nan_counts = other349.nan_counts; + __isset = other349.__isset; return *this; } -ColumnIndex& ColumnIndex::operator=(ColumnIndex&& other345) noexcept { - null_pages = std::move(other345.null_pages); - min_values = std::move(other345.min_values); - max_values = std::move(other345.max_values); - boundary_order = other345.boundary_order; - null_counts = std::move(other345.null_counts); - repetition_level_histograms = std::move(other345.repetition_level_histograms); - definition_level_histograms = std::move(other345.definition_level_histograms); - nan_counts = std::move(other345.nan_counts); - __isset = other345.__isset; +ColumnIndex& ColumnIndex::operator=(ColumnIndex&& other350) noexcept { + null_pages = std::move(other350.null_pages); + min_values = std::move(other350.min_values); + max_values = std::move(other350.max_values); + boundary_order = other350.boundary_order; + null_counts = std::move(other350.null_counts); + repetition_level_histograms = std::move(other350.repetition_level_histograms); + definition_level_histograms = std::move(other350.definition_level_histograms); + nan_counts = std::move(other350.nan_counts); + __isset = other350.__isset; return *this; } void ColumnIndex::printTo(std::ostream& out) const { @@ -5251,30 +5407,30 @@ bool AesGcmV1::operator==(const AesGcmV1 & rhs) const return true; } -AesGcmV1::AesGcmV1(const AesGcmV1& other346) { - aad_prefix = other346.aad_prefix; - aad_file_unique = other346.aad_file_unique; - supply_aad_prefix = other346.supply_aad_prefix; - __isset = other346.__isset; +AesGcmV1::AesGcmV1(const AesGcmV1& other351) { + aad_prefix = other351.aad_prefix; + aad_file_unique = other351.aad_file_unique; + supply_aad_prefix = other351.supply_aad_prefix; + __isset = other351.__isset; } -AesGcmV1::AesGcmV1(AesGcmV1&& other347) noexcept { - aad_prefix = std::move(other347.aad_prefix); - aad_file_unique = std::move(other347.aad_file_unique); - supply_aad_prefix = other347.supply_aad_prefix; - __isset = other347.__isset; +AesGcmV1::AesGcmV1(AesGcmV1&& other352) noexcept { + aad_prefix = std::move(other352.aad_prefix); + aad_file_unique = std::move(other352.aad_file_unique); + supply_aad_prefix = other352.supply_aad_prefix; + __isset = other352.__isset; } -AesGcmV1& AesGcmV1::operator=(const AesGcmV1& other348) { - aad_prefix = other348.aad_prefix; - aad_file_unique = other348.aad_file_unique; - supply_aad_prefix = other348.supply_aad_prefix; - __isset = other348.__isset; +AesGcmV1& AesGcmV1::operator=(const AesGcmV1& other353) { + aad_prefix = other353.aad_prefix; + aad_file_unique = other353.aad_file_unique; + supply_aad_prefix = other353.supply_aad_prefix; + __isset = other353.__isset; return *this; } -AesGcmV1& AesGcmV1::operator=(AesGcmV1&& other349) noexcept { - aad_prefix = std::move(other349.aad_prefix); - aad_file_unique = std::move(other349.aad_file_unique); - supply_aad_prefix = other349.supply_aad_prefix; - __isset = other349.__isset; +AesGcmV1& AesGcmV1::operator=(AesGcmV1&& other354) noexcept { + aad_prefix = std::move(other354.aad_prefix); + aad_file_unique = std::move(other354.aad_file_unique); + supply_aad_prefix = other354.supply_aad_prefix; + __isset = other354.__isset; return *this; } void AesGcmV1::printTo(std::ostream& out) const { @@ -5342,30 +5498,30 @@ bool AesGcmCtrV1::operator==(const AesGcmCtrV1 & rhs) const return true; } -AesGcmCtrV1::AesGcmCtrV1(const AesGcmCtrV1& other350) { - aad_prefix = other350.aad_prefix; - aad_file_unique = other350.aad_file_unique; - supply_aad_prefix = other350.supply_aad_prefix; - __isset = other350.__isset; +AesGcmCtrV1::AesGcmCtrV1(const AesGcmCtrV1& other355) { + aad_prefix = other355.aad_prefix; + aad_file_unique = other355.aad_file_unique; + supply_aad_prefix = other355.supply_aad_prefix; + __isset = other355.__isset; } -AesGcmCtrV1::AesGcmCtrV1(AesGcmCtrV1&& other351) noexcept { - aad_prefix = std::move(other351.aad_prefix); - aad_file_unique = std::move(other351.aad_file_unique); - supply_aad_prefix = other351.supply_aad_prefix; - __isset = other351.__isset; +AesGcmCtrV1::AesGcmCtrV1(AesGcmCtrV1&& other356) noexcept { + aad_prefix = std::move(other356.aad_prefix); + aad_file_unique = std::move(other356.aad_file_unique); + supply_aad_prefix = other356.supply_aad_prefix; + __isset = other356.__isset; } -AesGcmCtrV1& AesGcmCtrV1::operator=(const AesGcmCtrV1& other352) { - aad_prefix = other352.aad_prefix; - aad_file_unique = other352.aad_file_unique; - supply_aad_prefix = other352.supply_aad_prefix; - __isset = other352.__isset; +AesGcmCtrV1& AesGcmCtrV1::operator=(const AesGcmCtrV1& other357) { + aad_prefix = other357.aad_prefix; + aad_file_unique = other357.aad_file_unique; + supply_aad_prefix = other357.supply_aad_prefix; + __isset = other357.__isset; return *this; } -AesGcmCtrV1& AesGcmCtrV1::operator=(AesGcmCtrV1&& other353) noexcept { - aad_prefix = std::move(other353.aad_prefix); - aad_file_unique = std::move(other353.aad_file_unique); - supply_aad_prefix = other353.supply_aad_prefix; - __isset = other353.__isset; +AesGcmCtrV1& AesGcmCtrV1::operator=(AesGcmCtrV1&& other358) noexcept { + aad_prefix = std::move(other358.aad_prefix); + aad_file_unique = std::move(other358.aad_file_unique); + supply_aad_prefix = other358.supply_aad_prefix; + __isset = other358.__isset; return *this; } void AesGcmCtrV1::printTo(std::ostream& out) const { @@ -5420,26 +5576,26 @@ bool EncryptionAlgorithm::operator==(const EncryptionAlgorithm & rhs) const return true; } -EncryptionAlgorithm::EncryptionAlgorithm(const EncryptionAlgorithm& other354) { - AES_GCM_V1 = other354.AES_GCM_V1; - AES_GCM_CTR_V1 = other354.AES_GCM_CTR_V1; - __isset = other354.__isset; +EncryptionAlgorithm::EncryptionAlgorithm(const EncryptionAlgorithm& other359) { + AES_GCM_V1 = other359.AES_GCM_V1; + AES_GCM_CTR_V1 = other359.AES_GCM_CTR_V1; + __isset = other359.__isset; } -EncryptionAlgorithm::EncryptionAlgorithm(EncryptionAlgorithm&& other355) noexcept { - AES_GCM_V1 = std::move(other355.AES_GCM_V1); - AES_GCM_CTR_V1 = std::move(other355.AES_GCM_CTR_V1); - __isset = other355.__isset; +EncryptionAlgorithm::EncryptionAlgorithm(EncryptionAlgorithm&& other360) noexcept { + AES_GCM_V1 = std::move(other360.AES_GCM_V1); + AES_GCM_CTR_V1 = std::move(other360.AES_GCM_CTR_V1); + __isset = other360.__isset; } -EncryptionAlgorithm& EncryptionAlgorithm::operator=(const EncryptionAlgorithm& other356) { - AES_GCM_V1 = other356.AES_GCM_V1; - AES_GCM_CTR_V1 = other356.AES_GCM_CTR_V1; - __isset = other356.__isset; +EncryptionAlgorithm& EncryptionAlgorithm::operator=(const EncryptionAlgorithm& other361) { + AES_GCM_V1 = other361.AES_GCM_V1; + AES_GCM_CTR_V1 = other361.AES_GCM_CTR_V1; + __isset = other361.__isset; return *this; } -EncryptionAlgorithm& EncryptionAlgorithm::operator=(EncryptionAlgorithm&& other357) noexcept { - AES_GCM_V1 = std::move(other357.AES_GCM_V1); - AES_GCM_CTR_V1 = std::move(other357.AES_GCM_CTR_V1); - __isset = other357.__isset; +EncryptionAlgorithm& EncryptionAlgorithm::operator=(EncryptionAlgorithm&& other362) noexcept { + AES_GCM_V1 = std::move(other362.AES_GCM_V1); + AES_GCM_CTR_V1 = std::move(other362.AES_GCM_CTR_V1); + __isset = other362.__isset; return *this; } void EncryptionAlgorithm::printTo(std::ostream& out) const { @@ -5555,54 +5711,54 @@ bool FileMetaData::operator==(const FileMetaData & rhs) const return true; } -FileMetaData::FileMetaData(const FileMetaData& other382) { - version = other382.version; - schema = other382.schema; - num_rows = other382.num_rows; - row_groups = other382.row_groups; - key_value_metadata = other382.key_value_metadata; - created_by = other382.created_by; - column_orders = other382.column_orders; - encryption_algorithm = other382.encryption_algorithm; - footer_signing_key_metadata = other382.footer_signing_key_metadata; - __isset = other382.__isset; -} -FileMetaData::FileMetaData(FileMetaData&& other383) noexcept { - version = other383.version; - schema = std::move(other383.schema); - num_rows = other383.num_rows; - row_groups = std::move(other383.row_groups); - key_value_metadata = std::move(other383.key_value_metadata); - created_by = std::move(other383.created_by); - column_orders = std::move(other383.column_orders); - encryption_algorithm = std::move(other383.encryption_algorithm); - footer_signing_key_metadata = std::move(other383.footer_signing_key_metadata); - __isset = other383.__isset; -} -FileMetaData& FileMetaData::operator=(const FileMetaData& other384) { - version = other384.version; - schema = other384.schema; - num_rows = other384.num_rows; - row_groups = other384.row_groups; - key_value_metadata = other384.key_value_metadata; - created_by = other384.created_by; - column_orders = other384.column_orders; - encryption_algorithm = other384.encryption_algorithm; - footer_signing_key_metadata = other384.footer_signing_key_metadata; - __isset = other384.__isset; +FileMetaData::FileMetaData(const FileMetaData& other387) { + version = other387.version; + schema = other387.schema; + num_rows = other387.num_rows; + row_groups = other387.row_groups; + key_value_metadata = other387.key_value_metadata; + created_by = other387.created_by; + column_orders = other387.column_orders; + encryption_algorithm = other387.encryption_algorithm; + footer_signing_key_metadata = other387.footer_signing_key_metadata; + __isset = other387.__isset; +} +FileMetaData::FileMetaData(FileMetaData&& other388) noexcept { + version = other388.version; + schema = std::move(other388.schema); + num_rows = other388.num_rows; + row_groups = std::move(other388.row_groups); + key_value_metadata = std::move(other388.key_value_metadata); + created_by = std::move(other388.created_by); + column_orders = std::move(other388.column_orders); + encryption_algorithm = std::move(other388.encryption_algorithm); + footer_signing_key_metadata = std::move(other388.footer_signing_key_metadata); + __isset = other388.__isset; +} +FileMetaData& FileMetaData::operator=(const FileMetaData& other389) { + version = other389.version; + schema = other389.schema; + num_rows = other389.num_rows; + row_groups = other389.row_groups; + key_value_metadata = other389.key_value_metadata; + created_by = other389.created_by; + column_orders = other389.column_orders; + encryption_algorithm = other389.encryption_algorithm; + footer_signing_key_metadata = other389.footer_signing_key_metadata; + __isset = other389.__isset; return *this; } -FileMetaData& FileMetaData::operator=(FileMetaData&& other385) noexcept { - version = other385.version; - schema = std::move(other385.schema); - num_rows = other385.num_rows; - row_groups = std::move(other385.row_groups); - key_value_metadata = std::move(other385.key_value_metadata); - created_by = std::move(other385.created_by); - column_orders = std::move(other385.column_orders); - encryption_algorithm = std::move(other385.encryption_algorithm); - footer_signing_key_metadata = std::move(other385.footer_signing_key_metadata); - __isset = other385.__isset; +FileMetaData& FileMetaData::operator=(FileMetaData&& other390) noexcept { + version = other390.version; + schema = std::move(other390.schema); + num_rows = other390.num_rows; + row_groups = std::move(other390.row_groups); + key_value_metadata = std::move(other390.key_value_metadata); + created_by = std::move(other390.created_by); + column_orders = std::move(other390.column_orders); + encryption_algorithm = std::move(other390.encryption_algorithm); + footer_signing_key_metadata = std::move(other390.footer_signing_key_metadata); + __isset = other390.__isset; return *this; } void FileMetaData::printTo(std::ostream& out) const { @@ -5661,26 +5817,26 @@ bool FileCryptoMetaData::operator==(const FileCryptoMetaData & rhs) const return true; } -FileCryptoMetaData::FileCryptoMetaData(const FileCryptoMetaData& other386) { - encryption_algorithm = other386.encryption_algorithm; - key_metadata = other386.key_metadata; - __isset = other386.__isset; +FileCryptoMetaData::FileCryptoMetaData(const FileCryptoMetaData& other391) { + encryption_algorithm = other391.encryption_algorithm; + key_metadata = other391.key_metadata; + __isset = other391.__isset; } -FileCryptoMetaData::FileCryptoMetaData(FileCryptoMetaData&& other387) noexcept { - encryption_algorithm = std::move(other387.encryption_algorithm); - key_metadata = std::move(other387.key_metadata); - __isset = other387.__isset; +FileCryptoMetaData::FileCryptoMetaData(FileCryptoMetaData&& other392) noexcept { + encryption_algorithm = std::move(other392.encryption_algorithm); + key_metadata = std::move(other392.key_metadata); + __isset = other392.__isset; } -FileCryptoMetaData& FileCryptoMetaData::operator=(const FileCryptoMetaData& other388) { - encryption_algorithm = other388.encryption_algorithm; - key_metadata = other388.key_metadata; - __isset = other388.__isset; +FileCryptoMetaData& FileCryptoMetaData::operator=(const FileCryptoMetaData& other393) { + encryption_algorithm = other393.encryption_algorithm; + key_metadata = other393.key_metadata; + __isset = other393.__isset; return *this; } -FileCryptoMetaData& FileCryptoMetaData::operator=(FileCryptoMetaData&& other389) noexcept { - encryption_algorithm = std::move(other389.encryption_algorithm); - key_metadata = std::move(other389.key_metadata); - __isset = other389.__isset; +FileCryptoMetaData& FileCryptoMetaData::operator=(FileCryptoMetaData&& other394) noexcept { + encryption_algorithm = std::move(other394.encryption_algorithm); + key_metadata = std::move(other394.key_metadata); + __isset = other394.__isset; return *this; } void FileCryptoMetaData::printTo(std::ostream& out) const { diff --git a/cpp/src/generated/parquet_types.h b/cpp/src/generated/parquet_types.h index 7dc3ccc2de2c..c3c6e2ea24db 100644 --- a/cpp/src/generated/parquet_types.h +++ b/cpp/src/generated/parquet_types.h @@ -297,7 +297,15 @@ struct Encoding { * Added in 2.8 for FLOAT and DOUBLE. * Support for INT32, INT64 and FIXED_LEN_BYTE_ARRAY added in 2.11. */ - BYTE_STREAM_SPLIT = 9 + BYTE_STREAM_SPLIT = 9, + /** + * Adaptive Lossless floating-Point encoding. Reserved by the Parquet format. + */ + ALP = 10, + /** + * Fast Static Symbol Table encoding for BYTE_ARRAY values. + */ + FSST = 11 }; }; @@ -340,7 +348,8 @@ struct PageType { DATA_PAGE = 0, INDEX_PAGE = 1, DICTIONARY_PAGE = 2, - DATA_PAGE_V2 = 3 + DATA_PAGE_V2 = 3, + SYMBOL_TABLE_PAGE = 4 }; }; @@ -350,6 +359,21 @@ std::ostream& operator<<(std::ostream& out, const PageType::type& val); std::string to_string(const PageType::type& val); +/** + * The representation used by a symbol table page. + */ +struct SymbolTableType { + enum type { + FSST = 0 + }; +}; + +extern const std::map _SymbolTableType_VALUES_TO_NAMES; + +std::ostream& operator<<(std::ostream& out, const SymbolTableType::type& val); + +std::string to_string(const SymbolTableType::type& val); + /** * Enum to annotate whether lists of min/max elements inside ColumnIndex * are ordered and if so, in which direction. @@ -444,6 +468,8 @@ class BloomFilterCompression; class BloomFilterHeader; +class SymbolTablePageHeader; + class PageHeader; class KeyValue; @@ -520,7 +546,7 @@ class SizeStatistics { * schema information multiplied by the number of non-null and null values. * The number of null/non-null values can be inferred from the histograms * below. - * + * * For example, if a column chunk is dictionary-encoded with dictionary * ["a", "bc", "cde"], and a data page contains the indices [0, 0, 1, 2], * then this value for that data page should be 7 (1 + 1 + 2 + 3). @@ -2456,13 +2482,62 @@ void swap(BloomFilterHeader &a, BloomFilterHeader &b) noexcept; std::ostream& operator<<(std::ostream& out, const BloomFilterHeader& obj); + +/** + * Header for a shared symbol table page. + */ +class SymbolTablePageHeader { + public: + + SymbolTablePageHeader(const SymbolTablePageHeader&) noexcept; + SymbolTablePageHeader(SymbolTablePageHeader&&) noexcept; + SymbolTablePageHeader& operator=(const SymbolTablePageHeader&) noexcept; + SymbolTablePageHeader& operator=(SymbolTablePageHeader&&) noexcept; + SymbolTablePageHeader() noexcept; + + ~SymbolTablePageHeader() noexcept; + /** + * The representation of the symbol table and its encoded values. + * + * @see SymbolTableType + */ + SymbolTableType::type type; + /** + * Whether the page body is compressed with the column chunk codec. + */ + bool is_compressed; + + void __set_type(const SymbolTableType::type val); + + void __set_is_compressed(const bool val); + + bool operator == (const SymbolTablePageHeader & rhs) const; + bool operator != (const SymbolTablePageHeader &rhs) const { + return !(*this == rhs); + } + + bool operator < (const SymbolTablePageHeader & ) const; + + template + uint32_t read(Protocol_* iprot); + template + uint32_t write(Protocol_* oprot) const; + + void printTo(std::ostream& out) const; +}; + +void swap(SymbolTablePageHeader &a, SymbolTablePageHeader &b) noexcept; + +std::ostream& operator<<(std::ostream& out, const SymbolTablePageHeader& obj); + typedef struct _PageHeader__isset { - _PageHeader__isset() : crc(false), data_page_header(false), index_page_header(false), dictionary_page_header(false), data_page_header_v2(false) {} + _PageHeader__isset() : crc(false), data_page_header(false), index_page_header(false), dictionary_page_header(false), data_page_header_v2(false), symbol_table_page_header(false) {} bool crc :1; bool data_page_header :1; bool index_page_header :1; bool dictionary_page_header :1; bool data_page_header_v2 :1; + bool symbol_table_page_header :1; } _PageHeader__isset; class PageHeader { @@ -2512,6 +2587,7 @@ class PageHeader { IndexPageHeader index_page_header; DictionaryPageHeader dictionary_page_header; DataPageHeaderV2 data_page_header_v2; + SymbolTablePageHeader symbol_table_page_header; _PageHeader__isset __isset; @@ -2531,6 +2607,8 @@ class PageHeader { void __set_data_page_header_v2(const DataPageHeaderV2& val); + void __set_symbol_table_page_header(const SymbolTablePageHeader& val); + bool operator == (const PageHeader & rhs) const; bool operator != (const PageHeader &rhs) const { return !(*this == rhs); @@ -2706,7 +2784,7 @@ void swap(PageEncodingStats &a, PageEncodingStats &b) noexcept; std::ostream& operator<<(std::ostream& out, const PageEncodingStats& obj); typedef struct _ColumnMetaData__isset { - _ColumnMetaData__isset() : key_value_metadata(false), index_page_offset(false), dictionary_page_offset(false), statistics(false), encoding_stats(false), bloom_filter_offset(false), bloom_filter_length(false), size_statistics(false), geospatial_statistics(false) {} + _ColumnMetaData__isset() : key_value_metadata(false), index_page_offset(false), dictionary_page_offset(false), statistics(false), encoding_stats(false), bloom_filter_offset(false), bloom_filter_length(false), size_statistics(false), geospatial_statistics(false), symbol_table_page_offset(false), symbol_table_page_length(false) {} bool key_value_metadata :1; bool index_page_offset :1; bool dictionary_page_offset :1; @@ -2716,6 +2794,8 @@ typedef struct _ColumnMetaData__isset { bool bloom_filter_length :1; bool size_statistics :1; bool geospatial_statistics :1; + bool symbol_table_page_offset :1; + bool symbol_table_page_length :1; } _ColumnMetaData__isset; /** @@ -2814,6 +2894,14 @@ class ColumnMetaData { * Optional statistics specific for Geometry and Geography logical types */ GeospatialStatistics geospatial_statistics; + /** + * Byte offset from the beginning of the file to the symbol table page. + */ + int64_t symbol_table_page_offset; + /** + * Serialized symbol table page length, including its page header. + */ + int32_t symbol_table_page_length; _ColumnMetaData__isset __isset; @@ -2851,6 +2939,10 @@ class ColumnMetaData { void __set_geospatial_statistics(const GeospatialStatistics& val); + void __set_symbol_table_page_offset(const int64_t val); + + void __set_symbol_table_page_length(const int32_t val); + bool operator == (const ColumnMetaData & rhs) const; bool operator != (const ColumnMetaData &rhs) const { return !(*this == rhs); diff --git a/cpp/src/generated/parquet_types.tcc b/cpp/src/generated/parquet_types.tcc index 01559f897372..74ea71131343 100644 --- a/cpp/src/generated/parquet_types.tcc +++ b/cpp/src/generated/parquet_types.tcc @@ -2903,6 +2903,83 @@ uint32_t BloomFilterHeader::write(Protocol_* oprot) const { return xfer; } +template +uint32_t SymbolTablePageHeader::read(Protocol_* iprot) { + + ::apache::thrift::protocol::TInputRecursionTracker tracker(*iprot); + uint32_t xfer = 0; + std::string fname; + ::apache::thrift::protocol::TType ftype; + int16_t fid; + + xfer += iprot->readStructBegin(fname); + + using ::apache::thrift::protocol::TProtocolException; + + bool isset_type = false; + bool isset_is_compressed = false; + + while (true) + { + xfer += iprot->readFieldBegin(fname, ftype, fid); + if (ftype == ::apache::thrift::protocol::T_STOP) { + break; + } + switch (fid) + { + case 1: + if (ftype == ::apache::thrift::protocol::T_I32) { + int32_t ecast179; + xfer += iprot->readI32(ecast179); + this->type = static_cast(ecast179); + isset_type = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 2: + if (ftype == ::apache::thrift::protocol::T_BOOL) { + xfer += iprot->readBool(this->is_compressed); + isset_is_compressed = true; + } else { + xfer += iprot->skip(ftype); + } + break; + default: + xfer += iprot->skip(ftype); + break; + } + xfer += iprot->readFieldEnd(); + } + + xfer += iprot->readStructEnd(); + + if (!isset_type) + throw TProtocolException(TProtocolException::INVALID_DATA); + if (!isset_is_compressed) + throw TProtocolException(TProtocolException::INVALID_DATA); + return xfer; +} + +template +uint32_t SymbolTablePageHeader::write(Protocol_* oprot) const { + uint32_t xfer = 0; + ::apache::thrift::protocol::TOutputRecursionTracker tracker(*oprot); + xfer += oprot->writeStructBegin("SymbolTablePageHeader"); + + xfer += oprot->writeFieldBegin("type", ::apache::thrift::protocol::T_I32, 1); + xfer += oprot->writeI32(static_cast(this->type)); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldBegin("is_compressed", ::apache::thrift::protocol::T_BOOL, 2); + xfer += oprot->writeBool(this->is_compressed); + xfer += oprot->writeFieldEnd(); + + xfer += oprot->writeFieldStop(); + xfer += oprot->writeStructEnd(); + return xfer; +} + template uint32_t PageHeader::read(Protocol_* iprot) { @@ -2930,9 +3007,9 @@ uint32_t PageHeader::read(Protocol_* iprot) { { case 1: if (ftype == ::apache::thrift::protocol::T_I32) { - int32_t ecast179; - xfer += iprot->readI32(ecast179); - this->type = static_cast(ecast179); + int32_t ecast184; + xfer += iprot->readI32(ecast184); + this->type = static_cast(ecast184); isset_type = true; } else { xfer += iprot->skip(ftype); @@ -2994,6 +3071,14 @@ uint32_t PageHeader::read(Protocol_* iprot) { xfer += iprot->skip(ftype); } break; + case 9: + if (ftype == ::apache::thrift::protocol::T_STRUCT) { + xfer += this->symbol_table_page_header.read(iprot); + this->__isset.symbol_table_page_header = true; + } else { + xfer += iprot->skip(ftype); + } + break; default: xfer += iprot->skip(ftype); break; @@ -3055,6 +3140,11 @@ uint32_t PageHeader::write(Protocol_* oprot) const { xfer += this->data_page_header_v2.write(oprot); xfer += oprot->writeFieldEnd(); } + if (this->__isset.symbol_table_page_header) { + xfer += oprot->writeFieldBegin("symbol_table_page_header", ::apache::thrift::protocol::T_STRUCT, 9); + xfer += this->symbol_table_page_header.write(oprot); + xfer += oprot->writeFieldEnd(); + } xfer += oprot->writeFieldStop(); xfer += oprot->writeStructEnd(); return xfer; @@ -3250,9 +3340,9 @@ uint32_t PageEncodingStats::read(Protocol_* iprot) { { case 1: if (ftype == ::apache::thrift::protocol::T_I32) { - int32_t ecast192; - xfer += iprot->readI32(ecast192); - this->page_type = static_cast(ecast192); + int32_t ecast197; + xfer += iprot->readI32(ecast197); + this->page_type = static_cast(ecast197); isset_page_type = true; } else { xfer += iprot->skip(ftype); @@ -3260,9 +3350,9 @@ uint32_t PageEncodingStats::read(Protocol_* iprot) { break; case 2: if (ftype == ::apache::thrift::protocol::T_I32) { - int32_t ecast193; - xfer += iprot->readI32(ecast193); - this->encoding = static_cast(ecast193); + int32_t ecast198; + xfer += iprot->readI32(ecast198); + this->encoding = static_cast(ecast198); isset_encoding = true; } else { xfer += iprot->skip(ftype); @@ -3349,9 +3439,9 @@ uint32_t ColumnMetaData::read(Protocol_* iprot) { { case 1: if (ftype == ::apache::thrift::protocol::T_I32) { - int32_t ecast198; - xfer += iprot->readI32(ecast198); - this->type = static_cast(ecast198); + int32_t ecast203; + xfer += iprot->readI32(ecast203); + this->type = static_cast(ecast203); isset_type = true; } else { xfer += iprot->skip(ftype); @@ -3361,16 +3451,16 @@ uint32_t ColumnMetaData::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->encodings.clear(); - uint32_t _size199; - ::apache::thrift::protocol::TType _etype202; - xfer += iprot->readListBegin(_etype202, _size199); - this->encodings.resize(_size199); - uint32_t _i203; - for (_i203 = 0; _i203 < _size199; ++_i203) + uint32_t _size204; + ::apache::thrift::protocol::TType _etype207; + xfer += iprot->readListBegin(_etype207, _size204); + this->encodings.resize(_size204); + uint32_t _i208; + for (_i208 = 0; _i208 < _size204; ++_i208) { - int32_t ecast204; - xfer += iprot->readI32(ecast204); - this->encodings[_i203] = static_cast(ecast204); + int32_t ecast209; + xfer += iprot->readI32(ecast209); + this->encodings[_i208] = static_cast(ecast209); } xfer += iprot->readListEnd(); } @@ -3383,14 +3473,14 @@ uint32_t ColumnMetaData::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->path_in_schema.clear(); - uint32_t _size205; - ::apache::thrift::protocol::TType _etype208; - xfer += iprot->readListBegin(_etype208, _size205); - this->path_in_schema.resize(_size205); - uint32_t _i209; - for (_i209 = 0; _i209 < _size205; ++_i209) + uint32_t _size210; + ::apache::thrift::protocol::TType _etype213; + xfer += iprot->readListBegin(_etype213, _size210); + this->path_in_schema.resize(_size210); + uint32_t _i214; + for (_i214 = 0; _i214 < _size210; ++_i214) { - xfer += iprot->readString(this->path_in_schema[_i209]); + xfer += iprot->readString(this->path_in_schema[_i214]); } xfer += iprot->readListEnd(); } @@ -3401,9 +3491,9 @@ uint32_t ColumnMetaData::read(Protocol_* iprot) { break; case 4: if (ftype == ::apache::thrift::protocol::T_I32) { - int32_t ecast210; - xfer += iprot->readI32(ecast210); - this->codec = static_cast(ecast210); + int32_t ecast215; + xfer += iprot->readI32(ecast215); + this->codec = static_cast(ecast215); isset_codec = true; } else { xfer += iprot->skip(ftype); @@ -3437,14 +3527,14 @@ uint32_t ColumnMetaData::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->key_value_metadata.clear(); - uint32_t _size211; - ::apache::thrift::protocol::TType _etype214; - xfer += iprot->readListBegin(_etype214, _size211); - this->key_value_metadata.resize(_size211); - uint32_t _i215; - for (_i215 = 0; _i215 < _size211; ++_i215) + uint32_t _size216; + ::apache::thrift::protocol::TType _etype219; + xfer += iprot->readListBegin(_etype219, _size216); + this->key_value_metadata.resize(_size216); + uint32_t _i220; + for (_i220 = 0; _i220 < _size216; ++_i220) { - xfer += this->key_value_metadata[_i215].read(iprot); + xfer += this->key_value_metadata[_i220].read(iprot); } xfer += iprot->readListEnd(); } @@ -3489,14 +3579,14 @@ uint32_t ColumnMetaData::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->encoding_stats.clear(); - uint32_t _size216; - ::apache::thrift::protocol::TType _etype219; - xfer += iprot->readListBegin(_etype219, _size216); - this->encoding_stats.resize(_size216); - uint32_t _i220; - for (_i220 = 0; _i220 < _size216; ++_i220) + uint32_t _size221; + ::apache::thrift::protocol::TType _etype224; + xfer += iprot->readListBegin(_etype224, _size221); + this->encoding_stats.resize(_size221); + uint32_t _i225; + for (_i225 = 0; _i225 < _size221; ++_i225) { - xfer += this->encoding_stats[_i220].read(iprot); + xfer += this->encoding_stats[_i225].read(iprot); } xfer += iprot->readListEnd(); } @@ -3537,6 +3627,22 @@ uint32_t ColumnMetaData::read(Protocol_* iprot) { xfer += iprot->skip(ftype); } break; + case 18: + if (ftype == ::apache::thrift::protocol::T_I64) { + xfer += iprot->readI64(this->symbol_table_page_offset); + this->__isset.symbol_table_page_offset = true; + } else { + xfer += iprot->skip(ftype); + } + break; + case 19: + if (ftype == ::apache::thrift::protocol::T_I32) { + xfer += iprot->readI32(this->symbol_table_page_length); + this->__isset.symbol_table_page_length = true; + } else { + xfer += iprot->skip(ftype); + } + break; default: xfer += iprot->skip(ftype); break; @@ -3578,10 +3684,10 @@ uint32_t ColumnMetaData::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("encodings", ::apache::thrift::protocol::T_LIST, 2); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_I32, static_cast(this->encodings.size())); - std::vector ::const_iterator _iter221; - for (_iter221 = this->encodings.begin(); _iter221 != this->encodings.end(); ++_iter221) + std::vector ::const_iterator _iter226; + for (_iter226 = this->encodings.begin(); _iter226 != this->encodings.end(); ++_iter226) { - xfer += oprot->writeI32(static_cast((*_iter221))); + xfer += oprot->writeI32(static_cast((*_iter226))); } xfer += oprot->writeListEnd(); } @@ -3590,10 +3696,10 @@ uint32_t ColumnMetaData::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("path_in_schema", ::apache::thrift::protocol::T_LIST, 3); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRING, static_cast(this->path_in_schema.size())); - std::vector ::const_iterator _iter222; - for (_iter222 = this->path_in_schema.begin(); _iter222 != this->path_in_schema.end(); ++_iter222) + std::vector ::const_iterator _iter227; + for (_iter227 = this->path_in_schema.begin(); _iter227 != this->path_in_schema.end(); ++_iter227) { - xfer += oprot->writeString((*_iter222)); + xfer += oprot->writeString((*_iter227)); } xfer += oprot->writeListEnd(); } @@ -3619,10 +3725,10 @@ uint32_t ColumnMetaData::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("key_value_metadata", ::apache::thrift::protocol::T_LIST, 8); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast(this->key_value_metadata.size())); - std::vector ::const_iterator _iter223; - for (_iter223 = this->key_value_metadata.begin(); _iter223 != this->key_value_metadata.end(); ++_iter223) + std::vector ::const_iterator _iter228; + for (_iter228 = this->key_value_metadata.begin(); _iter228 != this->key_value_metadata.end(); ++_iter228) { - xfer += (*_iter223).write(oprot); + xfer += (*_iter228).write(oprot); } xfer += oprot->writeListEnd(); } @@ -3651,10 +3757,10 @@ uint32_t ColumnMetaData::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("encoding_stats", ::apache::thrift::protocol::T_LIST, 13); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast(this->encoding_stats.size())); - std::vector ::const_iterator _iter224; - for (_iter224 = this->encoding_stats.begin(); _iter224 != this->encoding_stats.end(); ++_iter224) + std::vector ::const_iterator _iter229; + for (_iter229 = this->encoding_stats.begin(); _iter229 != this->encoding_stats.end(); ++_iter229) { - xfer += (*_iter224).write(oprot); + xfer += (*_iter229).write(oprot); } xfer += oprot->writeListEnd(); } @@ -3680,6 +3786,16 @@ uint32_t ColumnMetaData::write(Protocol_* oprot) const { xfer += this->geospatial_statistics.write(oprot); xfer += oprot->writeFieldEnd(); } + if (this->__isset.symbol_table_page_offset) { + xfer += oprot->writeFieldBegin("symbol_table_page_offset", ::apache::thrift::protocol::T_I64, 18); + xfer += oprot->writeI64(this->symbol_table_page_offset); + xfer += oprot->writeFieldEnd(); + } + if (this->__isset.symbol_table_page_length) { + xfer += oprot->writeFieldBegin("symbol_table_page_length", ::apache::thrift::protocol::T_I32, 19); + xfer += oprot->writeI32(this->symbol_table_page_length); + xfer += oprot->writeFieldEnd(); + } xfer += oprot->writeFieldStop(); xfer += oprot->writeStructEnd(); return xfer; @@ -3752,14 +3868,14 @@ uint32_t EncryptionWithColumnKey::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->path_in_schema.clear(); - uint32_t _size233; - ::apache::thrift::protocol::TType _etype236; - xfer += iprot->readListBegin(_etype236, _size233); - this->path_in_schema.resize(_size233); - uint32_t _i237; - for (_i237 = 0; _i237 < _size233; ++_i237) + uint32_t _size238; + ::apache::thrift::protocol::TType _etype241; + xfer += iprot->readListBegin(_etype241, _size238); + this->path_in_schema.resize(_size238); + uint32_t _i242; + for (_i242 = 0; _i242 < _size238; ++_i242) { - xfer += iprot->readString(this->path_in_schema[_i237]); + xfer += iprot->readString(this->path_in_schema[_i242]); } xfer += iprot->readListEnd(); } @@ -3799,10 +3915,10 @@ uint32_t EncryptionWithColumnKey::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("path_in_schema", ::apache::thrift::protocol::T_LIST, 1); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRING, static_cast(this->path_in_schema.size())); - std::vector ::const_iterator _iter238; - for (_iter238 = this->path_in_schema.begin(); _iter238 != this->path_in_schema.end(); ++_iter238) + std::vector ::const_iterator _iter243; + for (_iter243 = this->path_in_schema.begin(); _iter243 != this->path_in_schema.end(); ++_iter243) { - xfer += oprot->writeString((*_iter238)); + xfer += oprot->writeString((*_iter243)); } xfer += oprot->writeListEnd(); } @@ -4082,14 +4198,14 @@ uint32_t RowGroup::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->columns.clear(); - uint32_t _size251; - ::apache::thrift::protocol::TType _etype254; - xfer += iprot->readListBegin(_etype254, _size251); - this->columns.resize(_size251); - uint32_t _i255; - for (_i255 = 0; _i255 < _size251; ++_i255) + uint32_t _size256; + ::apache::thrift::protocol::TType _etype259; + xfer += iprot->readListBegin(_etype259, _size256); + this->columns.resize(_size256); + uint32_t _i260; + for (_i260 = 0; _i260 < _size256; ++_i260) { - xfer += this->columns[_i255].read(iprot); + xfer += this->columns[_i260].read(iprot); } xfer += iprot->readListEnd(); } @@ -4118,14 +4234,14 @@ uint32_t RowGroup::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->sorting_columns.clear(); - uint32_t _size256; - ::apache::thrift::protocol::TType _etype259; - xfer += iprot->readListBegin(_etype259, _size256); - this->sorting_columns.resize(_size256); - uint32_t _i260; - for (_i260 = 0; _i260 < _size256; ++_i260) + uint32_t _size261; + ::apache::thrift::protocol::TType _etype264; + xfer += iprot->readListBegin(_etype264, _size261); + this->sorting_columns.resize(_size261); + uint32_t _i265; + for (_i265 = 0; _i265 < _size261; ++_i265) { - xfer += this->sorting_columns[_i260].read(iprot); + xfer += this->sorting_columns[_i265].read(iprot); } xfer += iprot->readListEnd(); } @@ -4185,10 +4301,10 @@ uint32_t RowGroup::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("columns", ::apache::thrift::protocol::T_LIST, 1); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast(this->columns.size())); - std::vector ::const_iterator _iter261; - for (_iter261 = this->columns.begin(); _iter261 != this->columns.end(); ++_iter261) + std::vector ::const_iterator _iter266; + for (_iter266 = this->columns.begin(); _iter266 != this->columns.end(); ++_iter266) { - xfer += (*_iter261).write(oprot); + xfer += (*_iter266).write(oprot); } xfer += oprot->writeListEnd(); } @@ -4206,10 +4322,10 @@ uint32_t RowGroup::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("sorting_columns", ::apache::thrift::protocol::T_LIST, 4); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast(this->sorting_columns.size())); - std::vector ::const_iterator _iter262; - for (_iter262 = this->sorting_columns.begin(); _iter262 != this->sorting_columns.end(); ++_iter262) + std::vector ::const_iterator _iter267; + for (_iter267 = this->sorting_columns.begin(); _iter267 != this->sorting_columns.end(); ++_iter267) { - xfer += (*_iter262).write(oprot); + xfer += (*_iter267).write(oprot); } xfer += oprot->writeListEnd(); } @@ -4503,14 +4619,14 @@ uint32_t OffsetIndex::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->page_locations.clear(); - uint32_t _size283; - ::apache::thrift::protocol::TType _etype286; - xfer += iprot->readListBegin(_etype286, _size283); - this->page_locations.resize(_size283); - uint32_t _i287; - for (_i287 = 0; _i287 < _size283; ++_i287) + uint32_t _size288; + ::apache::thrift::protocol::TType _etype291; + xfer += iprot->readListBegin(_etype291, _size288); + this->page_locations.resize(_size288); + uint32_t _i292; + for (_i292 = 0; _i292 < _size288; ++_i292) { - xfer += this->page_locations[_i287].read(iprot); + xfer += this->page_locations[_i292].read(iprot); } xfer += iprot->readListEnd(); } @@ -4523,14 +4639,14 @@ uint32_t OffsetIndex::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->unencoded_byte_array_data_bytes.clear(); - uint32_t _size288; - ::apache::thrift::protocol::TType _etype291; - xfer += iprot->readListBegin(_etype291, _size288); - this->unencoded_byte_array_data_bytes.resize(_size288); - uint32_t _i292; - for (_i292 = 0; _i292 < _size288; ++_i292) + uint32_t _size293; + ::apache::thrift::protocol::TType _etype296; + xfer += iprot->readListBegin(_etype296, _size293); + this->unencoded_byte_array_data_bytes.resize(_size293); + uint32_t _i297; + for (_i297 = 0; _i297 < _size293; ++_i297) { - xfer += iprot->readI64(this->unencoded_byte_array_data_bytes[_i292]); + xfer += iprot->readI64(this->unencoded_byte_array_data_bytes[_i297]); } xfer += iprot->readListEnd(); } @@ -4562,10 +4678,10 @@ uint32_t OffsetIndex::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("page_locations", ::apache::thrift::protocol::T_LIST, 1); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast(this->page_locations.size())); - std::vector ::const_iterator _iter293; - for (_iter293 = this->page_locations.begin(); _iter293 != this->page_locations.end(); ++_iter293) + std::vector ::const_iterator _iter298; + for (_iter298 = this->page_locations.begin(); _iter298 != this->page_locations.end(); ++_iter298) { - xfer += (*_iter293).write(oprot); + xfer += (*_iter298).write(oprot); } xfer += oprot->writeListEnd(); } @@ -4575,10 +4691,10 @@ uint32_t OffsetIndex::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("unencoded_byte_array_data_bytes", ::apache::thrift::protocol::T_LIST, 2); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_I64, static_cast(this->unencoded_byte_array_data_bytes.size())); - std::vector ::const_iterator _iter294; - for (_iter294 = this->unencoded_byte_array_data_bytes.begin(); _iter294 != this->unencoded_byte_array_data_bytes.end(); ++_iter294) + std::vector ::const_iterator _iter299; + for (_iter299 = this->unencoded_byte_array_data_bytes.begin(); _iter299 != this->unencoded_byte_array_data_bytes.end(); ++_iter299) { - xfer += oprot->writeI64((*_iter294)); + xfer += oprot->writeI64((*_iter299)); } xfer += oprot->writeListEnd(); } @@ -4619,14 +4735,14 @@ uint32_t ColumnIndex::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->null_pages.clear(); - uint32_t _size299; - ::apache::thrift::protocol::TType _etype302; - xfer += iprot->readListBegin(_etype302, _size299); - this->null_pages.resize(_size299); - uint32_t _i303; - for (_i303 = 0; _i303 < _size299; ++_i303) + uint32_t _size304; + ::apache::thrift::protocol::TType _etype307; + xfer += iprot->readListBegin(_etype307, _size304); + this->null_pages.resize(_size304); + uint32_t _i308; + for (_i308 = 0; _i308 < _size304; ++_i308) { - xfer += iprot->readBool(this->null_pages[_i303]); + xfer += iprot->readBool(this->null_pages[_i308]); } xfer += iprot->readListEnd(); } @@ -4639,14 +4755,14 @@ uint32_t ColumnIndex::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->min_values.clear(); - uint32_t _size304; - ::apache::thrift::protocol::TType _etype307; - xfer += iprot->readListBegin(_etype307, _size304); - this->min_values.resize(_size304); - uint32_t _i308; - for (_i308 = 0; _i308 < _size304; ++_i308) + uint32_t _size309; + ::apache::thrift::protocol::TType _etype312; + xfer += iprot->readListBegin(_etype312, _size309); + this->min_values.resize(_size309); + uint32_t _i313; + for (_i313 = 0; _i313 < _size309; ++_i313) { - xfer += iprot->readBinary(this->min_values[_i308]); + xfer += iprot->readBinary(this->min_values[_i313]); } xfer += iprot->readListEnd(); } @@ -4659,14 +4775,14 @@ uint32_t ColumnIndex::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->max_values.clear(); - uint32_t _size309; - ::apache::thrift::protocol::TType _etype312; - xfer += iprot->readListBegin(_etype312, _size309); - this->max_values.resize(_size309); - uint32_t _i313; - for (_i313 = 0; _i313 < _size309; ++_i313) + uint32_t _size314; + ::apache::thrift::protocol::TType _etype317; + xfer += iprot->readListBegin(_etype317, _size314); + this->max_values.resize(_size314); + uint32_t _i318; + for (_i318 = 0; _i318 < _size314; ++_i318) { - xfer += iprot->readBinary(this->max_values[_i313]); + xfer += iprot->readBinary(this->max_values[_i318]); } xfer += iprot->readListEnd(); } @@ -4677,9 +4793,9 @@ uint32_t ColumnIndex::read(Protocol_* iprot) { break; case 4: if (ftype == ::apache::thrift::protocol::T_I32) { - int32_t ecast314; - xfer += iprot->readI32(ecast314); - this->boundary_order = static_cast(ecast314); + int32_t ecast319; + xfer += iprot->readI32(ecast319); + this->boundary_order = static_cast(ecast319); isset_boundary_order = true; } else { xfer += iprot->skip(ftype); @@ -4689,14 +4805,14 @@ uint32_t ColumnIndex::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->null_counts.clear(); - uint32_t _size315; - ::apache::thrift::protocol::TType _etype318; - xfer += iprot->readListBegin(_etype318, _size315); - this->null_counts.resize(_size315); - uint32_t _i319; - for (_i319 = 0; _i319 < _size315; ++_i319) + uint32_t _size320; + ::apache::thrift::protocol::TType _etype323; + xfer += iprot->readListBegin(_etype323, _size320); + this->null_counts.resize(_size320); + uint32_t _i324; + for (_i324 = 0; _i324 < _size320; ++_i324) { - xfer += iprot->readI64(this->null_counts[_i319]); + xfer += iprot->readI64(this->null_counts[_i324]); } xfer += iprot->readListEnd(); } @@ -4709,14 +4825,14 @@ uint32_t ColumnIndex::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->repetition_level_histograms.clear(); - uint32_t _size320; - ::apache::thrift::protocol::TType _etype323; - xfer += iprot->readListBegin(_etype323, _size320); - this->repetition_level_histograms.resize(_size320); - uint32_t _i324; - for (_i324 = 0; _i324 < _size320; ++_i324) + uint32_t _size325; + ::apache::thrift::protocol::TType _etype328; + xfer += iprot->readListBegin(_etype328, _size325); + this->repetition_level_histograms.resize(_size325); + uint32_t _i329; + for (_i329 = 0; _i329 < _size325; ++_i329) { - xfer += iprot->readI64(this->repetition_level_histograms[_i324]); + xfer += iprot->readI64(this->repetition_level_histograms[_i329]); } xfer += iprot->readListEnd(); } @@ -4729,14 +4845,14 @@ uint32_t ColumnIndex::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->definition_level_histograms.clear(); - uint32_t _size325; - ::apache::thrift::protocol::TType _etype328; - xfer += iprot->readListBegin(_etype328, _size325); - this->definition_level_histograms.resize(_size325); - uint32_t _i329; - for (_i329 = 0; _i329 < _size325; ++_i329) + uint32_t _size330; + ::apache::thrift::protocol::TType _etype333; + xfer += iprot->readListBegin(_etype333, _size330); + this->definition_level_histograms.resize(_size330); + uint32_t _i334; + for (_i334 = 0; _i334 < _size330; ++_i334) { - xfer += iprot->readI64(this->definition_level_histograms[_i329]); + xfer += iprot->readI64(this->definition_level_histograms[_i334]); } xfer += iprot->readListEnd(); } @@ -4749,14 +4865,14 @@ uint32_t ColumnIndex::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->nan_counts.clear(); - uint32_t _size330; - ::apache::thrift::protocol::TType _etype333; - xfer += iprot->readListBegin(_etype333, _size330); - this->nan_counts.resize(_size330); - uint32_t _i334; - for (_i334 = 0; _i334 < _size330; ++_i334) + uint32_t _size335; + ::apache::thrift::protocol::TType _etype338; + xfer += iprot->readListBegin(_etype338, _size335); + this->nan_counts.resize(_size335); + uint32_t _i339; + for (_i339 = 0; _i339 < _size335; ++_i339) { - xfer += iprot->readI64(this->nan_counts[_i334]); + xfer += iprot->readI64(this->nan_counts[_i339]); } xfer += iprot->readListEnd(); } @@ -4794,10 +4910,10 @@ uint32_t ColumnIndex::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("null_pages", ::apache::thrift::protocol::T_LIST, 1); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_BOOL, static_cast(this->null_pages.size())); - std::vector ::const_iterator _iter335; - for (_iter335 = this->null_pages.begin(); _iter335 != this->null_pages.end(); ++_iter335) + std::vector ::const_iterator _iter340; + for (_iter340 = this->null_pages.begin(); _iter340 != this->null_pages.end(); ++_iter340) { - xfer += oprot->writeBool((*_iter335)); + xfer += oprot->writeBool((*_iter340)); } xfer += oprot->writeListEnd(); } @@ -4806,10 +4922,10 @@ uint32_t ColumnIndex::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("min_values", ::apache::thrift::protocol::T_LIST, 2); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRING, static_cast(this->min_values.size())); - std::vector ::const_iterator _iter336; - for (_iter336 = this->min_values.begin(); _iter336 != this->min_values.end(); ++_iter336) + std::vector ::const_iterator _iter341; + for (_iter341 = this->min_values.begin(); _iter341 != this->min_values.end(); ++_iter341) { - xfer += oprot->writeBinary((*_iter336)); + xfer += oprot->writeBinary((*_iter341)); } xfer += oprot->writeListEnd(); } @@ -4818,10 +4934,10 @@ uint32_t ColumnIndex::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("max_values", ::apache::thrift::protocol::T_LIST, 3); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRING, static_cast(this->max_values.size())); - std::vector ::const_iterator _iter337; - for (_iter337 = this->max_values.begin(); _iter337 != this->max_values.end(); ++_iter337) + std::vector ::const_iterator _iter342; + for (_iter342 = this->max_values.begin(); _iter342 != this->max_values.end(); ++_iter342) { - xfer += oprot->writeBinary((*_iter337)); + xfer += oprot->writeBinary((*_iter342)); } xfer += oprot->writeListEnd(); } @@ -4835,10 +4951,10 @@ uint32_t ColumnIndex::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("null_counts", ::apache::thrift::protocol::T_LIST, 5); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_I64, static_cast(this->null_counts.size())); - std::vector ::const_iterator _iter338; - for (_iter338 = this->null_counts.begin(); _iter338 != this->null_counts.end(); ++_iter338) + std::vector ::const_iterator _iter343; + for (_iter343 = this->null_counts.begin(); _iter343 != this->null_counts.end(); ++_iter343) { - xfer += oprot->writeI64((*_iter338)); + xfer += oprot->writeI64((*_iter343)); } xfer += oprot->writeListEnd(); } @@ -4848,10 +4964,10 @@ uint32_t ColumnIndex::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("repetition_level_histograms", ::apache::thrift::protocol::T_LIST, 6); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_I64, static_cast(this->repetition_level_histograms.size())); - std::vector ::const_iterator _iter339; - for (_iter339 = this->repetition_level_histograms.begin(); _iter339 != this->repetition_level_histograms.end(); ++_iter339) + std::vector ::const_iterator _iter344; + for (_iter344 = this->repetition_level_histograms.begin(); _iter344 != this->repetition_level_histograms.end(); ++_iter344) { - xfer += oprot->writeI64((*_iter339)); + xfer += oprot->writeI64((*_iter344)); } xfer += oprot->writeListEnd(); } @@ -4861,10 +4977,10 @@ uint32_t ColumnIndex::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("definition_level_histograms", ::apache::thrift::protocol::T_LIST, 7); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_I64, static_cast(this->definition_level_histograms.size())); - std::vector ::const_iterator _iter340; - for (_iter340 = this->definition_level_histograms.begin(); _iter340 != this->definition_level_histograms.end(); ++_iter340) + std::vector ::const_iterator _iter345; + for (_iter345 = this->definition_level_histograms.begin(); _iter345 != this->definition_level_histograms.end(); ++_iter345) { - xfer += oprot->writeI64((*_iter340)); + xfer += oprot->writeI64((*_iter345)); } xfer += oprot->writeListEnd(); } @@ -4874,10 +4990,10 @@ uint32_t ColumnIndex::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("nan_counts", ::apache::thrift::protocol::T_LIST, 8); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_I64, static_cast(this->nan_counts.size())); - std::vector ::const_iterator _iter341; - for (_iter341 = this->nan_counts.begin(); _iter341 != this->nan_counts.end(); ++_iter341) + std::vector ::const_iterator _iter346; + for (_iter346 = this->nan_counts.begin(); _iter346 != this->nan_counts.end(); ++_iter346) { - xfer += oprot->writeI64((*_iter341)); + xfer += oprot->writeI64((*_iter346)); } xfer += oprot->writeListEnd(); } @@ -5165,14 +5281,14 @@ uint32_t FileMetaData::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->schema.clear(); - uint32_t _size358; - ::apache::thrift::protocol::TType _etype361; - xfer += iprot->readListBegin(_etype361, _size358); - this->schema.resize(_size358); - uint32_t _i362; - for (_i362 = 0; _i362 < _size358; ++_i362) + uint32_t _size363; + ::apache::thrift::protocol::TType _etype366; + xfer += iprot->readListBegin(_etype366, _size363); + this->schema.resize(_size363); + uint32_t _i367; + for (_i367 = 0; _i367 < _size363; ++_i367) { - xfer += this->schema[_i362].read(iprot); + xfer += this->schema[_i367].read(iprot); } xfer += iprot->readListEnd(); } @@ -5193,14 +5309,14 @@ uint32_t FileMetaData::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->row_groups.clear(); - uint32_t _size363; - ::apache::thrift::protocol::TType _etype366; - xfer += iprot->readListBegin(_etype366, _size363); - this->row_groups.resize(_size363); - uint32_t _i367; - for (_i367 = 0; _i367 < _size363; ++_i367) + uint32_t _size368; + ::apache::thrift::protocol::TType _etype371; + xfer += iprot->readListBegin(_etype371, _size368); + this->row_groups.resize(_size368); + uint32_t _i372; + for (_i372 = 0; _i372 < _size368; ++_i372) { - xfer += this->row_groups[_i367].read(iprot); + xfer += this->row_groups[_i372].read(iprot); } xfer += iprot->readListEnd(); } @@ -5213,14 +5329,14 @@ uint32_t FileMetaData::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->key_value_metadata.clear(); - uint32_t _size368; - ::apache::thrift::protocol::TType _etype371; - xfer += iprot->readListBegin(_etype371, _size368); - this->key_value_metadata.resize(_size368); - uint32_t _i372; - for (_i372 = 0; _i372 < _size368; ++_i372) + uint32_t _size373; + ::apache::thrift::protocol::TType _etype376; + xfer += iprot->readListBegin(_etype376, _size373); + this->key_value_metadata.resize(_size373); + uint32_t _i377; + for (_i377 = 0; _i377 < _size373; ++_i377) { - xfer += this->key_value_metadata[_i372].read(iprot); + xfer += this->key_value_metadata[_i377].read(iprot); } xfer += iprot->readListEnd(); } @@ -5241,14 +5357,14 @@ uint32_t FileMetaData::read(Protocol_* iprot) { if (ftype == ::apache::thrift::protocol::T_LIST) { { this->column_orders.clear(); - uint32_t _size373; - ::apache::thrift::protocol::TType _etype376; - xfer += iprot->readListBegin(_etype376, _size373); - this->column_orders.resize(_size373); - uint32_t _i377; - for (_i377 = 0; _i377 < _size373; ++_i377) + uint32_t _size378; + ::apache::thrift::protocol::TType _etype381; + xfer += iprot->readListBegin(_etype381, _size378); + this->column_orders.resize(_size378); + uint32_t _i382; + for (_i382 = 0; _i382 < _size378; ++_i382) { - xfer += this->column_orders[_i377].read(iprot); + xfer += this->column_orders[_i382].read(iprot); } xfer += iprot->readListEnd(); } @@ -5306,10 +5422,10 @@ uint32_t FileMetaData::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("schema", ::apache::thrift::protocol::T_LIST, 2); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast(this->schema.size())); - std::vector ::const_iterator _iter378; - for (_iter378 = this->schema.begin(); _iter378 != this->schema.end(); ++_iter378) + std::vector ::const_iterator _iter383; + for (_iter383 = this->schema.begin(); _iter383 != this->schema.end(); ++_iter383) { - xfer += (*_iter378).write(oprot); + xfer += (*_iter383).write(oprot); } xfer += oprot->writeListEnd(); } @@ -5322,10 +5438,10 @@ uint32_t FileMetaData::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("row_groups", ::apache::thrift::protocol::T_LIST, 4); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast(this->row_groups.size())); - std::vector ::const_iterator _iter379; - for (_iter379 = this->row_groups.begin(); _iter379 != this->row_groups.end(); ++_iter379) + std::vector ::const_iterator _iter384; + for (_iter384 = this->row_groups.begin(); _iter384 != this->row_groups.end(); ++_iter384) { - xfer += (*_iter379).write(oprot); + xfer += (*_iter384).write(oprot); } xfer += oprot->writeListEnd(); } @@ -5335,10 +5451,10 @@ uint32_t FileMetaData::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("key_value_metadata", ::apache::thrift::protocol::T_LIST, 5); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast(this->key_value_metadata.size())); - std::vector ::const_iterator _iter380; - for (_iter380 = this->key_value_metadata.begin(); _iter380 != this->key_value_metadata.end(); ++_iter380) + std::vector ::const_iterator _iter385; + for (_iter385 = this->key_value_metadata.begin(); _iter385 != this->key_value_metadata.end(); ++_iter385) { - xfer += (*_iter380).write(oprot); + xfer += (*_iter385).write(oprot); } xfer += oprot->writeListEnd(); } @@ -5353,10 +5469,10 @@ uint32_t FileMetaData::write(Protocol_* oprot) const { xfer += oprot->writeFieldBegin("column_orders", ::apache::thrift::protocol::T_LIST, 7); { xfer += oprot->writeListBegin(::apache::thrift::protocol::T_STRUCT, static_cast(this->column_orders.size())); - std::vector ::const_iterator _iter381; - for (_iter381 = this->column_orders.begin(); _iter381 != this->column_orders.end(); ++_iter381) + std::vector ::const_iterator _iter386; + for (_iter386 = this->column_orders.begin(); _iter386 != this->column_orders.end(); ++_iter386) { - xfer += (*_iter381).write(oprot); + xfer += (*_iter386).write(oprot); } xfer += oprot->writeListEnd(); } diff --git a/cpp/src/parquet/CMakeLists.txt b/cpp/src/parquet/CMakeLists.txt index 606dcdc0a9e3..e83de495b61b 100644 --- a/cpp/src/parquet/CMakeLists.txt +++ b/cpp/src/parquet/CMakeLists.txt @@ -150,6 +150,10 @@ endif() # # Library config +set(PARQUET_FSST_SOURCE_DIR "${ARROW_SOURCE_DIR}/src/arrow/vendored/fsst") +set(PARQUET_FSST_SRCS "${PARQUET_FSST_SOURCE_DIR}/libfsst.cpp" + "${PARQUET_FSST_SOURCE_DIR}/fsst_avx512.cpp") + set(PARQUET_SRCS arrow/fuzz_encoding_internal.cc arrow/fuzz_internal.cc @@ -174,6 +178,8 @@ set(PARQUET_SRCS exception.cc file_reader.cc file_writer.cc + fsst_internal.cc + ${PARQUET_FSST_SRCS} geospatial/statistics.cc geospatial/util_internal.cc geospatial/util_json_internal.cc @@ -193,6 +199,21 @@ set(PARQUET_SRCS stream_writer.cc types.cc) +set_source_files_properties(${PARQUET_FSST_SRCS} + PROPERTIES SKIP_UNITY_BUILD_INCLUSION ON) +set_property(SOURCE ${PARQUET_FSST_SRCS} + APPEND + PROPERTY COMPILE_OPTIONS + "$<$,$>:-Wno-error=shorten-64-to-32;-Wno-shorten-64-to-32>" + "$<$,$,$>:-Wno-error=missing-declarations;-Wno-missing-declarations>" + "$<$:/wd4244>") +if(ARROW_HAVE_RUNTIME_AVX512) + separate_arguments(PARQUET_FSST_AVX512_FLAGS NATIVE_COMMAND "${ARROW_AVX512_FLAG}") + set_property(SOURCE "${PARQUET_FSST_SOURCE_DIR}/fsst_avx512.cpp" + APPEND + PROPERTY COMPILE_OPTIONS ${PARQUET_FSST_AVX512_FLAGS}) +endif() + if(ARROW_HAVE_RUNTIME_AVX2) # AVX2 is used as a proxy for BMI2. list(APPEND diff --git a/cpp/src/parquet/arrow/arrow_reader_writer_test.cc b/cpp/src/parquet/arrow/arrow_reader_writer_test.cc index 2bdbc38b3647..e6049a55ad5f 100644 --- a/cpp/src/parquet/arrow/arrow_reader_writer_test.cc +++ b/cpp/src/parquet/arrow/arrow_reader_writer_test.cc @@ -470,6 +470,117 @@ void CheckConfiguredRoundtrip( } } +TEST(FsstEncoding, ArrowRoundTripAndMetadata) { + ::arrow::StringBuilder values_builder; + for (int i = 0; i < 1000; ++i) { + if (i % 17 == 0) { + ASSERT_OK(values_builder.AppendNull()); + } else { + ASSERT_OK(values_builder.Append("https://arrow.apache.org/parquet/fsst/record/" + + std::to_string(i % 23))); + } + } + ASSERT_OK_AND_ASSIGN(auto values, values_builder.Finish()); + auto table = ::arrow::Table::Make( + ::arrow::schema({::arrow::field("value", ::arrow::utf8())}), {values}); + + for (auto offset_encoding : + {FsstOffsetEncoding::PLAIN, FsstOffsetEncoding::DELTA_BINARY_PACKED}) { + for (auto page_version : {ParquetDataPageVersion::V1, ParquetDataPageVersion::V2}) { + ARROW_SCOPED_TRACE("offset encoding = ", offset_encoding, + ", page version = ", static_cast(page_version)); + auto properties = WriterProperties::Builder() + .disable_dictionary() + ->encoding(Encoding::FSST) + ->fsst_offset_encoding(offset_encoding) + ->data_page_version(page_version) + ->data_pagesize(512) + ->enable_write_page_index() + ->build(); + + CheckConfiguredRoundtrip(table, nullptr, properties); + + ASSERT_OK_AND_ASSIGN(auto buffer, + WriteTableToBuffer(table, table->num_rows(), properties)); + auto reader = + ParquetFileReader::Open(std::make_shared<::arrow::io::BufferReader>(buffer)); + auto column = reader->metadata()->RowGroup(0)->ColumnChunk(0); + ASSERT_TRUE(column->has_symbol_table_page()); + ASSERT_GT(column->symbol_table_page_offset(), 0); + ASSERT_GT(column->symbol_table_page_length(), 0); + ASSERT_THAT(column->encodings(), ::testing::Contains(Encoding::FSST)); + ASSERT_THAT(column->encoding_stats(), + ::testing::Contains(::testing::AllOf( + ::testing::Field(&PageEncodingStats::page_type, + PageType::SYMBOL_TABLE_PAGE), + ::testing::Field(&PageEncodingStats::encoding, Encoding::FSST), + ::testing::Field(&PageEncodingStats::count, 1)))); + } + } + + auto properties = WriterProperties::Builder() + .disable_dictionary() + ->encoding(Encoding::FSST) + ->data_pagesize(512) + ->build(); + std::shared_ptr multi_row_group_result; + ASSERT_NO_FATAL_FAILURE( + DoRoundtrip(table, /*row_group_size=*/127, &multi_row_group_result, properties)); + ::arrow::AssertTablesEqual(*table, *multi_row_group_result); +} + +TEST(FsstEncoding, ArrowNestedRoundTrip) { + auto type = ::arrow::list(::arrow::field("item", ::arrow::utf8())); + auto values = ::arrow::ArrayFromJSON( + type, + R"([["https://arrow.apache.org/a", null, "https://arrow.apache.org/b"], null, [], ["https://arrow.apache.org/c"], [null]])"); + auto table = + ::arrow::Table::Make(::arrow::schema({::arrow::field("values", type)}), {values}); + auto properties = WriterProperties::Builder() + .disable_dictionary() + ->encoding(Encoding::FSST) + ->data_pagesize(64) + ->enable_write_page_index() + ->build(); + CheckConfiguredRoundtrip(table, nullptr, properties); +} + +TEST(FsstEncoding, ArrowDictionaryOutput) { + ::arrow::StringBuilder values_builder; + for (int i = 0; i < 500; ++i) { + if (i % 13 == 0) { + ASSERT_OK(values_builder.AppendNull()); + } else { + ASSERT_OK(values_builder.Append("https://arrow.apache.org/fsst/value/" + + std::to_string(i % 31))); + } + } + ASSERT_OK_AND_ASSIGN(auto values, values_builder.Finish()); + auto table = ::arrow::Table::Make( + ::arrow::schema({::arrow::field("value", ::arrow::utf8())}), {values}); + auto properties = WriterProperties::Builder() + .disable_dictionary() + ->encoding(Encoding::FSST) + ->data_pagesize(512) + ->build(); + ASSERT_OK_AND_ASSIGN(auto buffer, + WriteTableToBuffer(table, table->num_rows(), properties)); + + ArrowReaderProperties reader_properties; + reader_properties.set_read_dictionary(0, true); + FileReaderBuilder builder; + ASSERT_OK(builder.Open(std::make_shared<::arrow::io::BufferReader>(buffer))); + std::unique_ptr reader; + ASSERT_OK(builder.properties(reader_properties)->Build(&reader)); + ASSERT_OK_AND_ASSIGN(auto actual, reader->ReadTable()); + ASSERT_EQ(actual->column(0)->type()->id(), ::arrow::Type::DICTIONARY); + ASSERT_EQ(actual->column(0)->num_chunks(), 1); + ASSERT_OK_AND_ASSIGN( + std::shared_ptr decoded, + ::arrow::compute::Cast(*actual->column(0)->chunk(0), ::arrow::utf8())); + ::arrow::AssertArraysEqual(*values, *decoded); +} + void DoSimpleRoundtrip(const std::shared_ptr
& table, bool use_threads, int64_t row_group_size, const std::vector& column_subset, std::shared_ptr
* out, diff --git a/cpp/src/parquet/column_page.h b/cpp/src/parquet/column_page.h index f7dbb2526a76..00f3941744f5 100644 --- a/cpp/src/parquet/column_page.h +++ b/cpp/src/parquet/column_page.h @@ -180,4 +180,22 @@ class DictionaryPage : public Page { bool is_sorted_; }; +/// A symbol table shared by all FSST-encoded data pages in a column chunk. +class SymbolTablePage : public Page { + public: + SymbolTablePage(const std::shared_ptr& buffer, + SymbolTableType::type symbol_table_type, bool is_compressed) + : Page(buffer, PageType::SYMBOL_TABLE_PAGE), + symbol_table_type_(symbol_table_type), + is_compressed_(is_compressed) {} + + SymbolTableType::type symbol_table_type() const { return symbol_table_type_; } + + bool is_compressed() const { return is_compressed_; } + + private: + SymbolTableType::type symbol_table_type_; + bool is_compressed_; +}; + } // namespace parquet diff --git a/cpp/src/parquet/column_reader.cc b/cpp/src/parquet/column_reader.cc index 162a72bd1576..27fda888c965 100644 --- a/cpp/src/parquet/column_reader.cc +++ b/cpp/src/parquet/column_reader.cc @@ -51,6 +51,7 @@ #include "parquet/encryption/encryption_internal.h" #include "parquet/encryption/internal_file_decryptor.h" #include "parquet/exception.h" +#include "parquet/fsst_internal.h" #include "parquet/level_comparison.h" #include "parquet/level_conversion.h" #include "parquet/properties.h" @@ -245,6 +246,21 @@ void CheckNumValuesInHeader(int num_values) { } } +void CheckSymbolTablePageHeader(const format::PageHeader& page_header) { + if (!page_header.__isset.symbol_table_page_header) { + throw ParquetException("Symbol table page is missing its page header"); + } + const auto symbol_table_type = LoadEnumSafe(&page_header.symbol_table_page_header.type); + if (symbol_table_type != SymbolTableType::FSST) { + throw ParquetException("Only FSST8 symbol tables are currently supported"); + } + if (page_header.uncompressed_page_size < 9 || + page_header.uncompressed_page_size > 2049) { + throw ParquetException("Invalid symbol table page body size: ", + page_header.uncompressed_page_size); + } +} + // ---------------------------------------------------------------------- // SerializedPageReader deserializes Thrift metadata and pages that have been // assembled in a serialized stream for storing in a Parquet files @@ -420,6 +436,8 @@ bool SerializedPageReader::ShouldSkipPage(EncodedStatistics* data_page_statistic const format::DictionaryPageHeader& dict_header = current_page_header_.dictionary_page_header; CheckNumValuesInHeader(dict_header.num_values); + } else if (page_type == PageType::SYMBOL_TABLE_PAGE) { + CheckSymbolTablePageHeader(current_page_header_); } else { // We don't know what this page type is. We're allowed to skip non-data // pages. @@ -534,6 +552,23 @@ std::shared_ptr SerializedPageReader::NextPage() { return std::make_shared(page_buffer, dict_header.num_values, LoadEnumSafe(&dict_header.encoding), is_sorted); + } else if (page_type == PageType::SYMBOL_TABLE_PAGE) { + crypto_ctx_.start_decrypt_with_dictionary_page = false; + const format::SymbolTablePageHeader& symbol_header = + current_page_header_.symbol_table_page_header; + if (symbol_header.is_compressed) { + if (decompressor_ == nullptr) { + throw ParquetException( + "Compressed symbol table page has no column compression codec"); + } + page_buffer = + DecompressIfNeeded(std::move(page_buffer), compressed_len, uncompressed_len); + } else if (compressed_len != uncompressed_len) { + throw ParquetException( + "Uncompressed symbol table page has mismatched page sizes"); + } + return std::make_shared( + page_buffer, LoadEnumSafe(&symbol_header.type), symbol_header.is_compressed); } else if (page_type == PageType::DATA_PAGE) { ++page_ordinal_; const format::DataPageHeader& header = current_page_header_.data_page_header; @@ -808,6 +843,9 @@ class ColumnReaderImplBase { if (current_page_->type() == PageType::DICTIONARY_PAGE) { ConfigureDictionary(static_cast(current_page_.get())); continue; + } else if (current_page_->type() == PageType::SYMBOL_TABLE_PAGE) { + ConfigureSymbolTable(static_cast(current_page_.get())); + continue; } else if (current_page_->type() == PageType::DATA_PAGE) { const auto* page = static_cast(current_page_.get()); const int64_t levels_byte_size = InitializeLevelDecoders( @@ -829,6 +867,10 @@ class ColumnReaderImplBase { } void ConfigureDictionary(const DictionaryPage* page) { + if (fsst_symbol_table_body_ != nullptr) { + throw ParquetException( + "Dictionary and symbol table pages cannot coexist in a column chunk"); + } int encoding = static_cast(page->encoding()); if (page->encoding() == Encoding::PLAIN_DICTIONARY || page->encoding() == Encoding::PLAIN) { @@ -860,10 +902,28 @@ class ColumnReaderImplBase { } new_dictionary_ = true; + dictionary_configured_ = true; current_decoder_.SetDecoder(decoders_[encoding].get()); ARROW_DCHECK(current_decoder_); } + void ConfigureSymbolTable(const SymbolTablePage* page) { + if (dictionary_configured_) { + throw ParquetException( + "Dictionary and symbol table pages cannot coexist in a column chunk"); + } + if (fsst_symbol_table_body_ != nullptr) { + throw ParquetException("Column cannot have more than one symbol table page"); + } + if (page->symbol_table_type() != SymbolTableType::FSST) { + throw ParquetException("Only FSST8 symbol tables are currently supported"); + } + fsst_symbol_table_type_ = page->symbol_table_type(); + PARQUET_ASSIGN_OR_THROW(fsst_symbol_table_body_, + page->buffer()->CopySlice(0, page->buffer()->size(), pool_)); + internal::FsstSymbolTable::Deserialize(fsst_symbol_table_body_); + } + // Initialize repetition and definition level decoders on the next data page. // If the data page includes repetition and definition levels, we @@ -978,6 +1038,25 @@ class ColumnReaderImplBase { break; } + case Encoding::FSST: { + if constexpr (!std::is_same_v) { + throw ParquetException("FSST encoding only supports BYTE_ARRAY"); + } else { + if (fsst_symbol_table_body_ == nullptr) { + throw ParquetException( + "FSST data page must be preceded by a symbol table page"); + } + auto decoder = MakeFsstDecoder(descr_, fsst_symbol_table_type_, + fsst_symbol_table_body_, pool_); + auto typed_decoder = std::unique_ptr( + dynamic_cast(decoder.release())); + ARROW_DCHECK(typed_decoder != nullptr); + current_decoder_.SetDecoder(typed_decoder.get()); + decoders_[static_cast(encoding)] = std::move(typed_decoder); + break; + } + } + case Encoding::RLE_DICTIONARY: throw ParquetException("Dictionary page must be before data page."); @@ -1038,6 +1117,7 @@ class ColumnReaderImplBase { /// Flag to signal when a new dictionary has been set, for the benefit of /// DictionaryRecordReader bool new_dictionary_ = false; + bool dictionary_configured_ = false; // The exposed encoding ExposedEncoding exposed_encoding_ = ExposedEncoding::NO_ENCODING; @@ -1047,7 +1127,24 @@ class ColumnReaderImplBase { // plain-encoded data. std::unordered_map> decoders_; - void ConsumeBufferedValues(int64_t num_values) { num_decoded_values_ += num_values; } + SymbolTableType::type fsst_symbol_table_type_ = SymbolTableType::UNDEFINED; + std::shared_ptr fsst_symbol_table_body_; + + void ResetFsstSymbolTable() { + fsst_symbol_table_type_ = SymbolTableType::UNDEFINED; + fsst_symbol_table_body_.reset(); + dictionary_configured_ = false; + } + + void ConsumeBufferedValues(int64_t num_values) { + num_decoded_values_ += num_values; + if (current_encoding_ == Encoding::FSST && + num_decoded_values_ == num_buffered_values_ && current_decoder_ && + current_decoder_->values_left() != 0) { + throw ParquetException( + "FSST physical value count does not match definition levels"); + } + } }; // ---------------------------------------------------------------------- @@ -1241,6 +1338,15 @@ int64_t TypedColumnReaderImpl::Skip(int64_t num_values_to_skip) { const int64_t available_values = this->available_values_current_page(); if (values_to_skip >= available_values) { values_to_skip -= available_values; + // FSST validates its physical value count when the logical page is + // consumed. Drain the already-decoded values before taking the generic + // whole-page skip fast path. + if (this->current_encoding_ == Encoding::FSST) { + const int physical_values = this->current_decoder_->values_left(); + if (this->current_decoder_.Skip(physical_values) != physical_values) { + ParquetException::EofException("Could not skip all FSST values in data page"); + } + } this->ConsumeBufferedValues(available_values); } else { // Skip within the current Page. Since `values_to_skip < available_values`, the @@ -1829,7 +1935,10 @@ class TypedRecordReader : public TypedColumnReaderImpl, const ColumnDescriptor* descr() const override { return this->descr_; } // Dictionary decoders must be reset when advancing row groups - void ResetDecoders() { this->decoders_.clear(); } + void ResetDecoders() { + this->decoders_.clear(); + this->ResetFsstSymbolTable(); + } virtual void ReadValuesSpaced(int64_t values_with_nulls, int64_t null_count) { uint8_t* valid_bits = valid_bits_->mutable_data(); diff --git a/cpp/src/parquet/column_reader_test.cc b/cpp/src/parquet/column_reader_test.cc index 87514d87db63..4c63fe55cdff 100644 --- a/cpp/src/parquet/column_reader_test.cc +++ b/cpp/src/parquet/column_reader_test.cc @@ -363,6 +363,69 @@ TEST_F(TestPrimitiveReader, TestReadValuesMissing) { ParquetException); } +TEST_F(TestPrimitiveReader, FsstPhysicalValueCountMustMatchDefinitionLevels) { + max_def_level_ = 1; + max_rep_level_ = 0; + NodePtr type = schema::ByteArray("a", Repetition::OPTIONAL); + const ColumnDescriptor descr(type, max_def_level_, max_rep_level_); + + // An empty FSST8 table is valid and encodes every input byte as an escape. + pages_.push_back(std::make_shared( + Buffer::FromVector(std::vector(9, 0)), SymbolTableType::FSST, + /*is_compressed=*/false)); + + // The page has two logical values but only one is defined. Its FSST header + // incorrectly claims two physical values ("a" and "b"). + const std::vector fsst_data = { + 0, // PLAIN offsets + 2, 0, 0, 0, // physical value count + 8, 0, 0, 0, // offset section size + 2, 0, 0, 0, // first end offset + 4, 0, 0, 0, // second end offset + 0xFF, 'a', 0xFF, 'b' // escaped values + }; + pages_.push_back(MakeDataPage( + &descr, /*values=*/{}, /*num_vals=*/2, Encoding::FSST, fsst_data.data(), + static_cast(fsst_data.size()), /*def_levels=*/{1, 0}, max_def_level_, + /*rep_levels=*/{}, max_rep_level_)); + + InitReader(&descr); + auto* reader = static_cast(reader_.get()); + std::vector def_levels(2); + std::vector values(1); + int64_t values_read = 0; + EXPECT_THROW(reader->ReadBatch(2, def_levels.data(), /*rep_levels=*/nullptr, + values.data(), &values_read), + ParquetException); +} + +TEST_F(TestPrimitiveReader, SkipWholeFsstPage) { + max_def_level_ = 1; + max_rep_level_ = 0; + NodePtr type = schema::ByteArray("a", Repetition::OPTIONAL); + const ColumnDescriptor descr(type, max_def_level_, max_rep_level_); + + pages_.push_back(std::make_shared( + Buffer::FromVector(std::vector(9, 0)), SymbolTableType::FSST, + /*is_compressed=*/false)); + const std::vector fsst_data = { + 0, // PLAIN offsets + 1, 0, 0, 0, // physical value count + 4, 0, 0, 0, // offset section size + 2, 0, 0, 0, // end offset + 0xFF, 'a' // escaped value + }; + pages_.push_back(MakeDataPage( + &descr, /*values=*/{}, /*num_vals=*/2, Encoding::FSST, fsst_data.data(), + static_cast(fsst_data.size()), /*def_levels=*/{1, 0}, max_def_level_, + /*rep_levels=*/{}, max_rep_level_)); + + InitReader(&descr); + auto* reader = static_cast(reader_.get()); + ASSERT_EQ(reader->Skip(2), 2); + ASSERT_FALSE(reader->HasNext()); +} + // GH-41321: When max_def_level > 0 or max_rep_level > 0, and // Page has more or less levels than the `num_values` in // PageHeader. We should detect and throw exception. diff --git a/cpp/src/parquet/column_writer.cc b/cpp/src/parquet/column_writer.cc index 653f28f64bde..03443cf56c3f 100644 --- a/cpp/src/parquet/column_writer.cc +++ b/cpp/src/parquet/column_writer.cc @@ -55,6 +55,7 @@ #include "parquet/encoding.h" #include "parquet/encryption/encryption_internal.h" #include "parquet/encryption/internal_file_encryptor.h" +#include "parquet/fsst_internal.h" #include "parquet/level_conversion.h" #include "parquet/metadata.h" #include "parquet/page_index.h" @@ -264,6 +265,10 @@ class SerializedPageWriter : public PageWriter { pool_(pool), num_values_(0), dictionary_page_offset_(0), + has_dictionary_page_(false), + symbol_table_page_offset_(0), + symbol_table_page_length_(0), + has_symbol_table_page_(false), data_page_offset_(0), total_uncompressed_size_(0), total_compressed_size_(0), @@ -284,6 +289,10 @@ class SerializedPageWriter : public PageWriter { } int64_t WriteDictionaryPage(const DictionaryPage& page) override { + if (has_symbol_table_page_) { + throw ParquetException( + "Dictionary and symbol table pages cannot coexist in a column chunk"); + } int64_t uncompressed_size = page.buffer()->size(); if (uncompressed_size > std::numeric_limits::max()) { throw ParquetException( @@ -338,6 +347,7 @@ class SerializedPageWriter : public PageWriter { if (dictionary_page_offset_ == 0) { dictionary_page_offset_ = start_pos; } + has_dictionary_page_ = true; if (meta_encryptor_) { UpdateEncryption(encryption::kDictionaryPageHeader); @@ -353,6 +363,83 @@ class SerializedPageWriter : public PageWriter { return uncompressed_size + header_size; } + int64_t WriteSymbolTablePage(const SymbolTablePage& page) override { + if (has_dictionary_page_) { + throw ParquetException( + "Dictionary and symbol table pages cannot coexist in a column chunk"); + } + if (has_symbol_table_page_) { + throw ParquetException("Column cannot have more than one symbol table page"); + } + if (page.symbol_table_type() != SymbolTableType::FSST) { + throw ParquetException("Only FSST8 symbol tables are currently supported"); + } + if (page.buffer() == nullptr) { + throw ParquetException("Symbol table page has no body"); + } + internal::FsstSymbolTable::Deserialize(page.buffer()); + const int64_t uncompressed_size = page.buffer()->size(); + if (uncompressed_size > std::numeric_limits::max()) { + throw ParquetException("Uncompressed symbol table page exceeds INT32_MAX"); + } + + std::shared_ptr output_data = page.buffer(); + bool is_compressed = false; + if (has_compressor() && uncompressed_size > 0) { + auto compressed = std::static_pointer_cast( + AllocateBuffer(pool_, uncompressed_size)); + Compress(*page.buffer(), compressed.get()); + if (compressed->size() < uncompressed_size) { + output_data = std::move(compressed); + is_compressed = true; + } + } + + const uint8_t* output_data_buffer = output_data->data(); + int32_t output_data_len = static_cast(output_data->size()); + if (data_encryptor_ != nullptr) { + UpdateEncryption(encryption::kDictionaryPage); + PARQUET_THROW_NOT_OK(encryption_buffer_->Resize( + data_encryptor_->CiphertextLength(output_data_len), false)); + output_data_len = + data_encryptor_->Encrypt(output_data->span_as(), + encryption_buffer_->mutable_span_as()); + output_data_buffer = encryption_buffer_->data(); + } + + format::SymbolTablePageHeader symbol_header; + symbol_header.__set_type(ToThrift(page.symbol_table_type())); + symbol_header.__set_is_compressed(is_compressed); + + format::PageHeader page_header; + page_header.__set_type(format::PageType::SYMBOL_TABLE_PAGE); + page_header.__set_uncompressed_page_size(static_cast(uncompressed_size)); + page_header.__set_compressed_page_size(output_data_len); + page_header.__set_symbol_table_page_header(symbol_header); + if (page_checksum_verification_) { + const uint32_t crc32 = + ::arrow::internal::crc32(0, output_data_buffer, output_data_len); + page_header.__set_crc(static_cast(crc32)); + } + + PARQUET_ASSIGN_OR_THROW(const int64_t start_pos, sink_->Tell()); + symbol_table_page_offset_ = start_pos; + has_symbol_table_page_ = true; + if (meta_encryptor_ != nullptr) { + UpdateEncryption(encryption::kDictionaryPageHeader); + } + const int64_t header_size = + thrift_serializer_->Serialize(&page_header, sink_.get(), meta_encryptor_.get()); + PARQUET_THROW_NOT_OK(sink_->Write(output_data_buffer, output_data_len)); + if (header_size + output_data_len > std::numeric_limits::max()) { + throw ParquetException("Serialized symbol table page exceeds INT32_MAX"); + } + symbol_table_page_length_ = static_cast(header_size + output_data_len); + total_uncompressed_size_ += uncompressed_size + header_size; + total_compressed_size_ += output_data_len + header_size; + return uncompressed_size + header_size; + } + void Close(bool has_dictionary, bool fallback) override { if (meta_encryptor_ != nullptr) { UpdateEncryption(encryption::kColumnMetaData); @@ -363,6 +450,7 @@ class SerializedPageWriter : public PageWriter { // index_page_offset = -1 since they are not supported metadata_->Finish(num_values_, dictionary_page_offset_, -1, data_page_offset_, + symbol_table_page_offset_, symbol_table_page_length_, total_compressed_size_, total_uncompressed_size_, has_dictionary, fallback, dict_encoding_stats_, data_encoding_stats_, meta_encryptor_); @@ -534,6 +622,12 @@ class SerializedPageWriter : public PageWriter { int64_t dictionary_page_offset() { return dictionary_page_offset_; } + int64_t symbol_table_page_offset() { return symbol_table_page_offset_; } + + bool has_symbol_table_page() { return has_symbol_table_page_; } + + int32_t symbol_table_page_length() { return symbol_table_page_length_; } + int64_t data_page_offset() { return data_page_offset_; } int64_t total_compressed_size() { return total_compressed_size_; } @@ -604,6 +698,10 @@ class SerializedPageWriter : public PageWriter { MemoryPool* pool_; int64_t num_values_; int64_t dictionary_page_offset_; + bool has_dictionary_page_; + int64_t symbol_table_page_offset_; + int32_t symbol_table_page_length_; + bool has_symbol_table_page_; int64_t data_page_offset_; // The uncompressed page size the page writer has already // written. @@ -664,6 +762,10 @@ class BufferedPageWriter : public PageWriter { return pager_->WriteDictionaryPage(page); } + int64_t WriteSymbolTablePage(const SymbolTablePage& page) override { + return pager_->WriteSymbolTablePage(page); + } + void Close(bool has_dictionary, bool fallback) override { if (pager_->meta_encryptor_ != nullptr) { pager_->UpdateEncryption(encryption::kColumnMetaData); @@ -673,8 +775,13 @@ class BufferedPageWriter : public PageWriter { // dictionary page offset should be 0 iff there are no dictionary pages auto dictionary_page_offset = has_dictionary_pages_ ? pager_->dictionary_page_offset() + final_position : 0; + const auto symbol_table_page_offset = + !pager_->has_symbol_table_page() + ? 0 + : pager_->symbol_table_page_offset() + final_position; metadata_->Finish(pager_->num_values(), dictionary_page_offset, -1, pager_->data_page_offset() + final_position, + symbol_table_page_offset, pager_->symbol_table_page_length(), pager_->total_compressed_size(), pager_->total_uncompressed_size(), has_dictionary, fallback, pager_->dict_encoding_stats_, pager_->data_encoding_stats_, pager_->meta_encryptor_); @@ -793,6 +900,9 @@ class ColumnWriterImpl { // Serializes Dictionary Page if enabled virtual void WriteDictionaryPage() = 0; + // Finalize auxiliary encoding state after all buffered data pages are written. + virtual void FinalizeEncoding() {} + // A convenience struct to combine the encoded statistics and size statistics struct StatisticsPair { EncodedStatistics encoded_stats; @@ -1116,6 +1226,7 @@ int64_t ColumnWriterImpl::Close() { } FlushBufferedDataPages(); + FinalizeEncoding(); auto [chunk_statistics, chunk_size_statistics] = GetChunkStatistics(); chunk_statistics.ApplyStatSizeLimits( @@ -1289,8 +1400,14 @@ class TypedColumnWriterImpl : public ColumnWriterImpl, BloomFilter* bloom_filter) : ColumnWriterImpl(metadata, std::move(pager), use_dictionary, encoding, properties) { - current_encoder_ = MakeEncoder(ParquetType::type_num, encoding, use_dictionary, - descr_, properties->memory_pool()); + if (!use_dictionary && encoding == Encoding::FSST) { + current_encoder_ = + MakeFsstEncoder(descr_, properties->fsst_offset_encoding(descr_->path()), + properties->memory_pool()); + } else { + current_encoder_ = MakeEncoder(ParquetType::type_num, encoding, use_dictionary, + descr_, properties->memory_pool()); + } // We have to dynamic_cast as some compilers don't want to static_cast // through virtual inheritance. current_value_encoder_ = @@ -1507,7 +1624,30 @@ class TypedColumnWriterImpl : public ColumnWriterImpl, protected: std::shared_ptr GetValuesBuffer() override { - return current_encoder_->FlushValues(); + auto values = current_encoder_->FlushValues(); + if (current_encoder_->encoding() == Encoding::FSST) { + encoding_ = current_encoder_->page_encoding(); + if (!fsst_symbol_table_written_) { + auto symbol_table = current_encoder_->fsst_symbol_table(); + if (symbol_table == nullptr) { + throw ParquetException("FSST encoder did not provide a symbol table"); + } + SymbolTablePage page(symbol_table, current_encoder_->fsst_symbol_table_type(), + /*is_compressed=*/false); + total_bytes_written_ += pager_->WriteSymbolTablePage(page); + fsst_symbol_table_written_ = true; + } + } + return values; + } + + void FinalizeEncoding() override { + if (current_encoder_->encoding() == Encoding::FSST && !fsst_symbol_table_written_) { + auto values = GetValuesBuffer(); + if (values->size() != 0) { + throw ParquetException("FSST encoder retained values after the final data page"); + } + } } // Internal function to handle direct writing of ::arrow::DictionaryArray, @@ -1648,6 +1788,7 @@ class TypedColumnWriterImpl : public ColumnWriterImpl, using TypedStats = TypedStatistics; using BloomFilterWriter = TypedBloomFilterWriter; std::unique_ptr current_encoder_; + bool fsst_symbol_table_written_ = false; // Downcasted observers of current_encoder_. // The downcast is performed once as opposed to at every use since // dynamic_cast is so expensive, and static_cast is not available due diff --git a/cpp/src/parquet/column_writer.h b/cpp/src/parquet/column_writer.h index 5ad58c5ecf21..30e8ea407508 100644 --- a/cpp/src/parquet/column_writer.h +++ b/cpp/src/parquet/column_writer.h @@ -50,6 +50,7 @@ class ColumnDescriptor; class ColumnIndexBuilder; class DataPage; class DictionaryPage; +class SymbolTablePage; class Encryptor; class OffsetIndexBuilder; class WriterProperties; @@ -114,6 +115,11 @@ class PARQUET_EXPORT PageWriter { // Return the number of uncompressed bytes written (including header size) virtual int64_t WriteDictionaryPage(const DictionaryPage& page) = 0; + // Return the number of uncompressed bytes written (including header size) + virtual int64_t WriteSymbolTablePage(const SymbolTablePage& page) { + throw ParquetException("This page writer does not support symbol table pages"); + } + /// \brief The total number of bytes written as serialized data and /// dictionary pages to the sink so far. virtual int64_t total_compressed_bytes_written() const = 0; diff --git a/cpp/src/parquet/decoder.cc b/cpp/src/parquet/decoder.cc index c4d3fe5a8a5a..b3ea37de7705 100644 --- a/cpp/src/parquet/decoder.cc +++ b/cpp/src/parquet/decoder.cc @@ -50,6 +50,7 @@ #include "arrow/visit_data_inline.h" #include "parquet/exception.h" +#include "parquet/fsst_internal.h" #include "parquet/platform.h" #include "parquet/schema.h" #include "parquet/types.h" @@ -1706,6 +1707,175 @@ class DeltaBitPackDecoder : public TypedDecoderImpl { T last_value_; }; +// ---------------------------------------------------------------------- +// FSST decoder + +class FsstDecoder final : public TypedDecoderImpl { + public: + FsstDecoder(const ColumnDescriptor* descr, + const std::shared_ptr& symbol_table_body, MemoryPool* pool) + : TypedDecoderImpl(descr, Encoding::FSST), + pool_(pool), + symbol_table_(internal::FsstSymbolTable::Deserialize(symbol_table_body)) {} + + void SetData(int num_values, const uint8_t* data, int len) override; + + int Decode(ByteArray* buffer, int max_values) override { + max_values = std::min(max_values, this->num_values_); + for (int i = 0; i < max_values; ++i) { + const int32_t begin = decoded_offsets_[value_index_]; + const int32_t end = decoded_offsets_[value_index_ + 1]; + buffer[i].ptr = begin == end ? nullptr : decoded_data_.data() + begin; + buffer[i].len = static_cast(end - begin); + ++value_index_; + } + this->num_values_ -= max_values; + return max_values; + } + + int DecodeArrow(int num_values, int null_count, const uint8_t* valid_bits, + int64_t valid_bits_offset, + typename EncodingTraits::Accumulator* out) override { + const int physical_values = num_values - null_count; + std::vector values(physical_values); + const int decoded = Decode(values.data(), physical_values); + if (decoded != physical_values) { + throw ParquetException("Expected to decode ", physical_values, + " FSST values, but decoded ", decoded); + } + auto append_values = [&](auto* helper) { + int index = 0; + RETURN_NOT_OK(VisitBitRuns( + valid_bits, valid_bits_offset, num_values, + [&](int64_t position, int64_t run_length, bool is_valid) { + if (!is_valid) { + return helper->AppendNulls(run_length); + } + for (int64_t i = 0; i < run_length; ++i) { + RETURN_NOT_OK(helper->AppendValue(values[index].ptr, + static_cast(values[index].len))); + ++index; + } + return Status::OK(); + })); + return Status::OK(); + }; + PARQUET_THROW_NOT_OK(DispatchArrowBinaryHelper( + out, num_values, /*estimated_data_length=*/{}, append_values)); + return physical_values; + } + + int DecodeArrow(int num_values, int null_count, const uint8_t* valid_bits, + int64_t valid_bits_offset, + typename EncodingTraits::DictAccumulator* out) override { + const int physical_values = num_values - null_count; + std::vector values(physical_values); + const int decoded = Decode(values.data(), physical_values); + if (decoded != physical_values) { + throw ParquetException("Expected to decode ", physical_values, + " FSST values, but decoded ", decoded); + } + PARQUET_THROW_NOT_OK(out->Reserve(num_values)); + int index = 0; + VisitNullBitmapInline( + valid_bits, valid_bits_offset, num_values, null_count, + [&]() { + PARQUET_THROW_NOT_OK(out->Append(values[index].ptr, values[index].len)); + ++index; + }, + [&]() { PARQUET_THROW_NOT_OK(out->AppendNull()); }); + return physical_values; + } + + private: + MemoryPool* pool_; + std::shared_ptr symbol_table_; + std::vector decoded_offsets_; + std::vector decoded_data_; + size_t value_index_ = 0; +}; + +void FsstDecoder::SetData(int num_values, const uint8_t* data, int len) { + if (len < 9) { + throw ParquetException("FSST data section is shorter than its 9-byte header"); + } + const auto offset_encoding = static_cast(data[0]); + if (offset_encoding != FsstOffsetEncoding::PLAIN && + offset_encoding != FsstOffsetEncoding::DELTA_BINARY_PACKED) { + throw ParquetException("Unsupported FSST offset encoding ", + static_cast(data[0])); + } + const int32_t physical_values = + ::arrow::bit_util::FromLittleEndian(SafeLoadAs(data + 1)); + const int32_t offset_section_size = + ::arrow::bit_util::FromLittleEndian(SafeLoadAs(data + 5)); + if (physical_values < 0 || physical_values > num_values) { + throw ParquetException("Invalid FSST physical value count ", physical_values, + " for data page containing ", num_values, " values"); + } + if (offset_section_size < 0 || offset_section_size > len - 9) { + throw ParquetException("Invalid FSST offset section length ", offset_section_size); + } + + const uint8_t* offset_data = data + 9; + std::vector compressed_offsets; + if (offset_encoding == FsstOffsetEncoding::PLAIN) { + const int64_t expected_size = static_cast(physical_values) * sizeof(int32_t); + if (offset_section_size != expected_size) { + throw ParquetException("FSST PLAIN offset section has length ", offset_section_size, + "; expected ", expected_size); + } + compressed_offsets.resize(physical_values); + for (int32_t i = 0; i < physical_values; ++i) { + compressed_offsets[i] = ::arrow::bit_util::FromLittleEndian( + SafeLoadAs(offset_data + static_cast(i) * 4)); + } + } else if (physical_values == 0) { + if (offset_section_size != 0) { + throw ParquetException("Empty FSST page has a non-empty offset section"); + } + } else { + DeltaBitPackDecoder offset_decoder(nullptr, pool_); + offset_decoder.SetData(physical_values, offset_data, offset_section_size); + if (offset_decoder.ValidValuesCount() != physical_values) { + throw ParquetException("FSST delta offset count does not match value count"); + } + compressed_offsets.resize(physical_values); + if (offset_decoder.Decode(compressed_offsets.data(), physical_values) != + physical_values) { + throw ParquetException("FSST delta offset count does not match value count"); + } + } + + const uint8_t* compressed_data = offset_data + offset_section_size; + const int32_t compressed_data_size = len - 9 - offset_section_size; + int32_t previous_offset = 0; + decoded_offsets_.clear(); + decoded_offsets_.reserve(static_cast(physical_values) + 1); + decoded_offsets_.push_back(0); + decoded_data_.clear(); + std::vector decoded_value; + for (int32_t end_offset : compressed_offsets) { + if (end_offset < previous_offset || end_offset > compressed_data_size) { + throw ParquetException("FSST offsets are not monotonic or exceed data size"); + } + symbol_table_->Decompress(compressed_data + previous_offset, + end_offset - previous_offset, &decoded_value); + if (decoded_data_.size() > + static_cast(std::numeric_limits::max()) - decoded_value.size()) { + throw ParquetException("FSST decoded data exceeds INT32_MAX"); + } + decoded_data_.insert(decoded_data_.end(), decoded_value.begin(), decoded_value.end()); + decoded_offsets_.push_back(static_cast(decoded_data_.size())); + previous_offset = end_offset; + } + if (previous_offset != compressed_data_size) { + throw ParquetException("Final FSST offset does not equal compressed data size"); + } + this->num_values_ = physical_values; + value_index_ = 0; +} + // ---------------------------------------------------------------------- // DELTA_LENGTH_BYTE_ARRAY decoder @@ -2377,6 +2547,19 @@ class ByteStreamSplitDecoder : public ByteStreamSplitDecoderBase MakeFsstDecoder(const ColumnDescriptor* descr, + SymbolTableType::type symbol_table_type, + const std::shared_ptr& symbol_table_body, + ::arrow::MemoryPool* pool) { + if (descr == nullptr || descr->physical_type() != Type::BYTE_ARRAY) { + throw ParquetException("FSST decoder only supports BYTE_ARRAY"); + } + if (symbol_table_type != SymbolTableType::FSST) { + throw ParquetException("Only FSST8 symbol tables are currently supported"); + } + return std::make_unique(descr, symbol_table_body, pool); +} + std::unique_ptr MakeDecoder(Type::type type_num, Encoding::type encoding, const ColumnDescriptor* descr, ::arrow::MemoryPool* pool) { @@ -2448,6 +2631,8 @@ std::unique_ptr MakeDecoder(Type::type type_num, Encoding::type encodin return std::make_unique(descr); } throw ParquetException("RLE encoding only supports BOOLEAN"); + } else if (encoding == Encoding::FSST) { + throw ParquetException("FSST decoder requires a symbol table page"); } else { ParquetException::NYI("Selected encoding is not supported"); } diff --git a/cpp/src/parquet/encoder.cc b/cpp/src/parquet/encoder.cc index 3e469df277b8..0da4f60496df 100644 --- a/cpp/src/parquet/encoder.cc +++ b/cpp/src/parquet/encoder.cc @@ -46,6 +46,7 @@ #include "arrow/visit_data_inline.h" #include "parquet/exception.h" +#include "parquet/fsst_internal.h" #include "parquet/platform.h" #include "parquet/schema.h" #include "parquet/types.h" @@ -1294,6 +1295,205 @@ void DeltaBitPackEncoder::PutSpaced(const T* src, int num_values, } } +// ---------------------------------------------------------------------- +// FSST encoder + +class FsstEncoder final : public EncoderImpl, virtual public TypedEncoder { + public: + FsstEncoder(const ColumnDescriptor* descr, FsstOffsetEncoding::type offset_encoding, + MemoryPool* pool) + : EncoderImpl(descr, Encoding::FSST, pool), offset_encoding_(offset_encoding) { + if (offset_encoding != FsstOffsetEncoding::PLAIN && + offset_encoding != FsstOffsetEncoding::DELTA_BINARY_PACKED) { + throw ParquetException("Unsupported FSST offset encoding"); + } + } + + using TypedEncoder::Put; + + int64_t EstimatedDataEncodedSize() override { + return 9 + static_cast(values_.size()) * sizeof(int32_t) + values_byte_size_; + } + + std::shared_ptr FlushValues() override; + + Encoding::type page_encoding() const override { return page_encoding_; } + + std::shared_ptr fsst_symbol_table() const override { + return symbol_table_body_; + } + + SymbolTableType::type fsst_symbol_table_type() const override { + return SymbolTableType::FSST; + } + + void Put(const ByteArray* src, int num_values) override { + if (num_values < 0 || + static_cast(values_.size()) + static_cast(num_values) > + static_cast(std::numeric_limits::max())) { + throw ParquetException("FSST data page contains too many values"); + } + values_.reserve(values_.size() + num_values); + for (int i = 0; i < num_values; ++i) { + if (src[i].len != 0 && src[i].ptr == nullptr) { + throw ParquetException("FSST input value has a null data pointer"); + } + if (src[i].len > static_cast(std::numeric_limits::max()) || + values_byte_size_ > + std::numeric_limits::max() - static_cast(src[i].len)) { + throw ParquetException("FSST input data exceeds INT32_MAX"); + } + if (src[i].len == 0) { + values_.emplace_back(); + } else { + values_.emplace_back(reinterpret_cast(src[i].ptr), src[i].len); + } + values_byte_size_ += src[i].len; + unencoded_byte_array_data_bytes_ += src[i].len; + } + } + + void PutSpaced(const ByteArray* src, int num_values, const uint8_t* valid_bits, + int64_t valid_bits_offset) override { + if (valid_bits == nullptr) { + Put(src, num_values); + return; + } + for (int i = 0; i < num_values; ++i) { + if (::arrow::bit_util::GetBit(valid_bits, valid_bits_offset + i)) { + Put(src + i, 1); + } + } + } + + void Put(const ::arrow::Array& values) override { + AssertVarLengthBinary(values); + auto append = [&](std::string_view value) { + if (ARROW_PREDICT_FALSE(value.size() > kMaxByteArraySize)) { + return Status::Invalid( + "Parquet cannot store strings with size 2GB or more, got: ", value.size()); + } + ByteArray parquet_value(static_cast(value.size()), + reinterpret_cast(value.data())); + Put(&parquet_value, 1); + return Status::OK(); + }; + auto append_null = []() { return Status::OK(); }; + Status status; + if (::arrow::is_binary_like(values.type_id())) { + status = ::arrow::VisitArraySpanInline( + *values.data(), append, append_null); + } else if (::arrow::is_large_binary_like(values.type_id())) { + status = + ::arrow::VisitArraySpanInline( + *values.data(), append, append_null); + } else { + status = + ::arrow::VisitArraySpanInline( + *values.data(), append, append_null); + } + PARQUET_THROW_NOT_OK(status); + } + + private: + std::shared_ptr EncodePlain() const; + + const FsstOffsetEncoding::type offset_encoding_; + Encoding::type page_encoding_ = Encoding::FSST; + std::shared_ptr symbol_table_; + std::shared_ptr symbol_table_body_; + std::vector values_; + int64_t values_byte_size_ = 0; +}; + +std::shared_ptr FsstEncoder::EncodePlain() const { + ::arrow::BufferBuilder sink(pool_); + PARQUET_THROW_NOT_OK( + sink.Reserve(values_byte_size_ + static_cast(values_.size()) * 4)); + for (const std::string& value : values_) { + const uint32_t length = + ::arrow::bit_util::ToLittleEndian(static_cast(value.size())); + PARQUET_THROW_NOT_OK( + sink.Append(reinterpret_cast(&length), sizeof(length))); + PARQUET_THROW_NOT_OK(sink.Append(value.data(), value.size())); + } + std::shared_ptr output; + PARQUET_THROW_NOT_OK(sink.Finish(&output, true)); + return output; +} + +std::shared_ptr FsstEncoder::FlushValues() { + if (symbol_table_ == nullptr) { + symbol_table_ = internal::FsstSymbolTable::Train(values_); + symbol_table_body_ = symbol_table_->Serialize(pool_); + } + + std::vector end_offsets; + end_offsets.reserve(values_.size()); + const int64_t plain_size = + values_byte_size_ + static_cast(values_.size()) * sizeof(uint32_t); + const size_t maximum_compressed_data_size = + static_cast(plain_size > 9 ? plain_size - 9 : 0); + std::vector compressed_data; + if (!symbol_table_->CompressBatch(values_, maximum_compressed_data_size, &end_offsets, + &compressed_data)) { + page_encoding_ = Encoding::PLAIN; + auto output = EncodePlain(); + values_.clear(); + values_byte_size_ = 0; + return output; + } + + std::shared_ptr encoded_offsets; + if (offset_encoding_ == FsstOffsetEncoding::PLAIN || end_offsets.empty()) { + PARQUET_ASSIGN_OR_THROW( + auto offsets, + ::arrow::AllocateBuffer(end_offsets.size() * sizeof(int32_t), pool_)); + for (size_t i = 0; i < end_offsets.size(); ++i) { + ::arrow::util::SafeStore(offsets->mutable_data() + i * sizeof(int32_t), + ::arrow::bit_util::ToLittleEndian(end_offsets[i])); + } + encoded_offsets = std::move(offsets); + } else { + DeltaBitPackEncoder offset_encoder(nullptr, pool_); + offset_encoder.Put(end_offsets.data(), static_cast(end_offsets.size())); + encoded_offsets = offset_encoder.FlushValues(); + } + if (encoded_offsets->size() > std::numeric_limits::max()) { + throw ParquetException("FSST offset section exceeds INT32_MAX"); + } + + ::arrow::BufferBuilder sink(pool_); + PARQUET_THROW_NOT_OK(sink.Reserve(9 + encoded_offsets->size() + + static_cast(compressed_data.size()))); + const uint8_t offset_encoding = static_cast(offset_encoding_); + const int32_t num_values = + ::arrow::bit_util::ToLittleEndian(static_cast(values_.size())); + const int32_t offset_size = + ::arrow::bit_util::ToLittleEndian(static_cast(encoded_offsets->size())); + PARQUET_THROW_NOT_OK(sink.Append(&offset_encoding, 1)); + PARQUET_THROW_NOT_OK( + sink.Append(reinterpret_cast(&num_values), sizeof(num_values))); + PARQUET_THROW_NOT_OK( + sink.Append(reinterpret_cast(&offset_size), sizeof(offset_size))); + PARQUET_THROW_NOT_OK(sink.Append(encoded_offsets->data(), encoded_offsets->size())); + PARQUET_THROW_NOT_OK(sink.Append(compressed_data.data(), compressed_data.size())); + std::shared_ptr fsst_output; + PARQUET_THROW_NOT_OK(sink.Finish(&fsst_output, true)); + + std::shared_ptr output; + if (fsst_output->size() >= plain_size) { + page_encoding_ = Encoding::PLAIN; + output = EncodePlain(); + } else { + page_encoding_ = Encoding::FSST; + output = std::move(fsst_output); + } + values_.clear(); + values_byte_size_ = 0; + return output; +} + // ---------------------------------------------------------------------- // DELTA_LENGTH_BYTE_ARRAY encoder @@ -1766,6 +1966,15 @@ std::shared_ptr RleBooleanEncoder::FlushValues() { // ---------------------------------------------------------------------- // Factory function +std::unique_ptr MakeFsstEncoder(const ColumnDescriptor* descr, + FsstOffsetEncoding::type offset_encoding, + MemoryPool* pool) { + if (descr == nullptr || descr->physical_type() != Type::BYTE_ARRAY) { + throw ParquetException("FSST encoder only supports BYTE_ARRAY"); + } + return std::make_unique(descr, offset_encoding, pool); +} + std::unique_ptr MakeEncoder(Type::type type_num, Encoding::type encoding, bool use_dictionary, const ColumnDescriptor* descr, MemoryPool* pool) { diff --git a/cpp/src/parquet/encoding.h b/cpp/src/parquet/encoding.h index e3de4f2aa60b..a9dc3f5f28f1 100644 --- a/cpp/src/parquet/encoding.h +++ b/cpp/src/parquet/encoding.h @@ -162,6 +162,18 @@ class Encoder { virtual std::shared_ptr FlushValues() = 0; virtual Encoding::type encoding() const = 0; + /// The encoding used by the most recently flushed data page. This can + /// differ from encoding() for encoders, such as FSST, that can fall back to + /// PLAIN on a page-by-page basis. + virtual Encoding::type page_encoding() const { return encoding(); } + + /// The serialized shared symbol table, when this encoder has produced an + /// FSST page. + virtual std::shared_ptr fsst_symbol_table() const { return nullptr; } + virtual SymbolTableType::type fsst_symbol_table_type() const { + return SymbolTableType::UNDEFINED; + } + virtual void Put(const ::arrow::Array& values) = 0; // Report the number of bytes written to the encoder since the last report. @@ -424,6 +436,11 @@ std::unique_ptr MakeEncoder( const ColumnDescriptor* descr = NULLPTR, ::arrow::MemoryPool* pool = ::arrow::default_memory_pool()); +PARQUET_EXPORT +std::unique_ptr MakeFsstEncoder( + const ColumnDescriptor* descr, FsstOffsetEncoding::type offset_encoding, + ::arrow::MemoryPool* pool = ::arrow::default_memory_pool()); + template std::unique_ptr::Encoder> MakeTypedEncoder( Encoding::type encoding, bool use_dictionary = false, @@ -440,6 +457,12 @@ std::unique_ptr MakeDecoder( Type::type type_num, Encoding::type encoding, const ColumnDescriptor* descr = NULLPTR, ::arrow::MemoryPool* pool = ::arrow::default_memory_pool()); +PARQUET_EXPORT +std::unique_ptr MakeFsstDecoder( + const ColumnDescriptor* descr, SymbolTableType::type symbol_table_type, + const std::shared_ptr& symbol_table_body, + ::arrow::MemoryPool* pool = ::arrow::default_memory_pool()); + namespace detail { PARQUET_EXPORT diff --git a/cpp/src/parquet/encoding_test.cc b/cpp/src/parquet/encoding_test.cc index 831829e4a210..352acc0951d2 100644 --- a/cpp/src/parquet/encoding_test.cc +++ b/cpp/src/parquet/encoding_test.cc @@ -43,6 +43,7 @@ #include "arrow/util/endian.h" #include "arrow/util/string.h" #include "parquet/encoding.h" +#include "parquet/fsst_internal.h" #include "parquet/platform.h" #include "parquet/schema.h" #include "parquet/test_util.h" @@ -2660,4 +2661,214 @@ TEST(DeltaByteArrayEncodingAdHoc, ArrowDirectPut) { } } +class FsstEncodingTest : public ::testing::TestWithParam {}; + +TEST_P(FsstEncodingTest, RoundTrip) { + const auto offset_encoding = GetParam(); + auto node = schema::ByteArray("value", Repetition::REQUIRED); + ColumnDescriptor descr(node, /*max_definition_level=*/0, + /*max_repetition_level=*/0); + auto base_encoder = MakeFsstEncoder(&descr, offset_encoding); + auto* encoder = dynamic_cast(base_encoder.get()); + ASSERT_NE(encoder, nullptr); + + const std::vector input = { + "https://arrow.apache.org/docs/parquet/fsst/alpha", + "https://arrow.apache.org/docs/parquet/fsst/beta", + "https://arrow.apache.org/docs/parquet/fsst/gamma", + "https://arrow.apache.org/docs/parquet/fsst/alpha", + "https://arrow.apache.org/docs/parquet/fsst/beta", + }; + std::vector parquet_input; + for (const auto& value : input) { + parquet_input.emplace_back(static_cast(value.size()), + reinterpret_cast(value.data())); + } + encoder->Put(parquet_input.data(), static_cast(parquet_input.size())); + auto encoded = encoder->FlushValues(); + ASSERT_EQ(base_encoder->page_encoding(), Encoding::FSST); + ASSERT_NE(base_encoder->fsst_symbol_table(), nullptr); + + auto base_decoder = + MakeFsstDecoder(&descr, SymbolTableType::FSST, base_encoder->fsst_symbol_table()); + auto* decoder = dynamic_cast(base_decoder.get()); + ASSERT_NE(decoder, nullptr); + decoder->SetData(static_cast(input.size()), encoded->data(), + static_cast(encoded->size())); + std::vector output(input.size()); + ASSERT_EQ(decoder->Decode(output.data(), static_cast(output.size())), + output.size()); + for (size_t i = 0; i < input.size(); ++i) { + ASSERT_EQ( + std::string_view(reinterpret_cast(output[i].ptr), output[i].len), + input[i]); + } +} + +INSTANTIATE_TEST_SUITE_P(Fsst8WithBothOffsetEncodings, FsstEncodingTest, + ::testing::Values(FsstOffsetEncoding::PLAIN, + FsstOffsetEncoding::DELTA_BINARY_PACKED)); + +TEST(FsstEncoding, FallsBackToPlainWhenEncodingExpandsPage) { + auto node = schema::ByteArray("value", Repetition::REQUIRED); + ColumnDescriptor descr(node, 0, 0); + auto base_encoder = MakeFsstEncoder(&descr, FsstOffsetEncoding::PLAIN); + auto* encoder = dynamic_cast(base_encoder.get()); + const std::string value = "x"; + const ByteArray input(static_cast(value.size()), + reinterpret_cast(value.data())); + encoder->Put(&input, 1); + const auto encoded = encoder->FlushValues(); + ASSERT_EQ(base_encoder->page_encoding(), Encoding::PLAIN); + ASSERT_EQ(encoded->size(), 5); +} + +TEST(FsstEncoding, EmptyTrainingCorpusCanEncodeLaterValues) { + const auto table = internal::FsstSymbolTable::Train(/*values=*/{}); + ASSERT_LE(table->symbol_count(), 1); + ASSERT_GE(table->Serialize(::arrow::default_memory_pool())->size(), 9); + + std::vector compressed; + ASSERT_TRUE(table->Compress("abc", /*max_output_size=*/6, &compressed)); + ASSERT_EQ(compressed, (std::vector{0xFF, 'a', 0xFF, 'b', 0xFF, 'c'})); + + std::vector decompressed; + table->Decompress(compressed.data(), compressed.size(), &decompressed); + ASSERT_EQ(decompressed, (std::vector{'a', 'b', 'c'})); +} + +TEST(FsstEncoding, EveryTrainedSymbolIsReachable) { + std::vector training_values; + for (int pattern = 0; pattern < 400; ++pattern) { + std::string value = "common-prefix-" + std::to_string(pattern) + "-"; + value.push_back(static_cast(pattern & 0xFF)); + value += "-common-suffix"; + for (int repetition = 0; repetition < 8; ++repetition) { + training_values.push_back(value); + } + } + + const auto table = internal::FsstSymbolTable::Train(training_values); + ASSERT_GT(table->symbol_count(), 0); + for (uint32_t code = 0; code < table->symbol_count(); ++code) { + std::vector compressed; + ASSERT_TRUE(table->Compress(table->symbol(code), /*max_output_size=*/1, &compressed)) + << "symbol code " << code; + ASSERT_EQ(compressed.size(), 1) << "symbol code " << code; + } +} + +TEST(FsstEncoding, TrainedTableUsesPortableLengthOrderedLayout) { + const std::vector training_values = { + "https://arrow.apache.org/parquet/fsst/alpha", + "https://arrow.apache.org/parquet/fsst/beta", + "https://arrow.apache.org/parquet/fsst/gamma"}; + const auto table = internal::FsstSymbolTable::Train(training_values); + const auto body = table->Serialize(::arrow::default_memory_pool()); + + ASSERT_EQ(body->data()[0], table->symbol_count()); + size_t code = 0; + size_t symbol_offset = 9; + for (size_t length = 1; length <= 8; ++length) { + const size_t length_count = body->data()[length]; + for (size_t i = 0; i < length_count; ++i, ++code) { + ASSERT_LT(code, table->symbol_count()); + ASSERT_EQ(table->symbol(static_cast(code)).size(), length); + ASSERT_EQ(std::memcmp(body->data() + symbol_offset, + table->symbol(static_cast(code)).data(), length), + 0); + symbol_offset += length; + } + } + ASSERT_EQ(code, table->symbol_count()); + ASSERT_EQ(symbol_offset, body->size()); +} + +TEST(FsstEncoding, DecodesSpecificationExample) { + auto node = schema::ByteArray("value", Repetition::REQUIRED); + ColumnDescriptor descr(node, 0, 0); + + const std::vector table = { + 6, 0, 0, 0, 0, 2, 0, 2, 2, '/', 'p', 'a', 'g', 'e', '/', 'd', 'a', + 't', 'a', 'h', 't', 't', 'p', ':', '/', '/', 'e', 'x', 'a', 'm', 'p', 'l', 'e', 'h', + 't', 't', 'p', 's', ':', '/', '/', 't', 'e', 's', 't', '.', 'c', 'o', 'm'}; + auto table_buffer = std::make_shared(table.data(), table.size()); + + const std::vector page = {0, // PLAIN offsets + 4, 0, 0, 0, // Four physical values + 16, 0, 0, 0, // Offset section size + 5, 0, 0, 0, // End offsets + 10, 0, 0, 0, 13, 0, 0, 0, 15, 0, + 0, 0, 4, 3, 0, 0xFF, '1', // Encoded values + 4, 3, 0, 0xFF, '2', 4, 5, 1, 2, 3}; + auto decoder = MakeFsstDecoder(&descr, SymbolTableType::FSST, table_buffer); + decoder->SetData(4, page.data(), static_cast(page.size())); + auto* typed_decoder = dynamic_cast(decoder.get()); + ASSERT_NE(typed_decoder, nullptr); + std::vector actual(4); + ASSERT_EQ(typed_decoder->Decode(actual.data(), 4), 4); + const std::vector expected = {"https://example/page1", + "https://example/page2", + "https://test.com/data", "http://example"}; + for (size_t i = 0; i < expected.size(); ++i) { + ASSERT_EQ(static_cast(actual[i]), expected[i]); + } +} + +TEST(FsstEncoding, RejectsUnsupportedSymbolTableType) { + auto node = schema::ByteArray("value", Repetition::REQUIRED); + ColumnDescriptor descr(node, 0, 0); + std::vector table(34, 0); + auto table_buffer = std::make_shared(table.data(), table.size()); + ASSERT_THROW(MakeFsstDecoder(&descr, SymbolTableType::UNDEFINED, table_buffer), + ParquetException); +} + +TEST(FsstEncoding, RejectsMalformedTablesAndValues) { + auto node = schema::ByteArray("value", Repetition::REQUIRED); + ColumnDescriptor descr(node, 0, 0); + + std::vector invalid_table(9, 0); + invalid_table[0] = 1; // Symbol count does not match the empty histogram. + auto invalid_table_buffer = + std::make_shared(invalid_table.data(), invalid_table.size()); + ASSERT_THROW(MakeFsstDecoder(&descr, SymbolTableType::FSST, invalid_table_buffer), + ParquetException); + + std::vector table(10, 0); + table[0] = 1; + table[1] = 1; + table[9] = 'a'; + auto table_buffer = std::make_shared(table.data(), table.size()); + + auto SetSingleCompressedValue = [&](uint8_t code) { + std::vector page(14, 0); + page[0] = static_cast(FsstOffsetEncoding::PLAIN); + page[1] = 1; // One physical value. + page[5] = 4; // Four bytes of PLAIN end offsets. + page[9] = 1; // The only value ends after one compressed byte. + page[13] = code; + auto decoder = MakeFsstDecoder(&descr, SymbolTableType::FSST, table_buffer); + decoder->SetData(1, page.data(), static_cast(page.size())); + }; + ASSERT_THROW(SetSingleCompressedValue(1), ParquetException); // Invalid code. + ASSERT_THROW(SetSingleCompressedValue(0xFF), ParquetException); // Truncated escape. +} + +TEST(FsstEncoding, RejectsImpossiblePlainOffsetsBeforeAllocation) { + auto node = schema::ByteArray("value", Repetition::REQUIRED); + ColumnDescriptor descr(node, 0, 0); + auto table_buffer = Buffer::FromVector(std::vector(9, 0)); + auto decoder = MakeFsstDecoder(&descr, SymbolTableType::FSST, table_buffer); + + std::vector page(9, 0); + page[0] = static_cast(FsstOffsetEncoding::PLAIN); + const int32_t physical_values = + ::arrow::bit_util::ToLittleEndian(std::numeric_limits::max()); + std::memcpy(page.data() + 1, &physical_values, sizeof(physical_values)); + ASSERT_THROW(decoder->SetData(std::numeric_limits::max(), page.data(), + static_cast(page.size())), + ParquetException); +} + } // namespace parquet::test diff --git a/cpp/src/parquet/file_deserialize_test.cc b/cpp/src/parquet/file_deserialize_test.cc index e81f7689a6cc..ee70a3c50467 100644 --- a/cpp/src/parquet/file_deserialize_test.cc +++ b/cpp/src/parquet/file_deserialize_test.cc @@ -190,6 +190,21 @@ class TestPageSerde : public ::testing::Test { ASSERT_NO_THROW(serializer.Serialize(&page_header_, out_stream_.get())); } + void WriteSymbolTablePageHeader(format::SymbolTableType::type type, + int32_t uncompressed_size, int32_t compressed_size, + bool is_compressed = false) { + format::SymbolTablePageHeader symbol_table_page_header; + symbol_table_page_header.__set_type(type); + symbol_table_page_header.__set_is_compressed(is_compressed); + page_header_.__set_symbol_table_page_header(symbol_table_page_header); + page_header_.uncompressed_page_size = uncompressed_size; + page_header_.compressed_page_size = compressed_size; + page_header_.type = format::PageType::SYMBOL_TABLE_PAGE; + + ThriftSerializer serializer; + ASSERT_NO_THROW(serializer.Serialize(&page_header_, out_stream_.get())); + } + void WriteIndexPageHeader(int32_t uncompressed_size = 0, int32_t compressed_size = 0) { page_header_.__set_index_page_header(index_page_header_); page_header_.uncompressed_page_size = uncompressed_size; @@ -634,6 +649,28 @@ TEST_F(TestPageSerde, DoesNotFilterDictionaryPages) { ASSERT_EQ(page_reader_->NextPage(), nullptr); } +TEST_F(TestPageSerde, RejectsInvalidSymbolTablePageBodySize) { + ASSERT_NO_FATAL_FAILURE(WriteSymbolTablePageHeader(format::SymbolTableType::FSST, + /*uncompressed_size=*/8, + /*compressed_size=*/0)); + InitSerializedPageReader(/*num_rows=*/1); + ASSERT_THROW(page_reader_->NextPage(), ParquetException); + + ResetStream(); + ASSERT_NO_FATAL_FAILURE(WriteSymbolTablePageHeader(format::SymbolTableType::FSST, + /*uncompressed_size=*/2050, + /*compressed_size=*/0)); + InitSerializedPageReader(/*num_rows=*/1); + ASSERT_THROW(page_reader_->NextPage(), ParquetException); + + ResetStream(); + ASSERT_NO_FATAL_FAILURE(WriteSymbolTablePageHeader( + static_cast(1), /*uncompressed_size=*/33, + /*compressed_size=*/0)); + InitSerializedPageReader(/*num_rows=*/1); + ASSERT_THROW(page_reader_->NextPage(), ParquetException); +} + // Tests that we successfully skip non-data pages. TEST_F(TestPageSerde, SkipsNonDataPages) { int data_size = 1024; diff --git a/cpp/src/parquet/file_reader.cc b/cpp/src/parquet/file_reader.cc index 2f46a5e296f8..41fe55170afa 100644 --- a/cpp/src/parquet/file_reader.cc +++ b/cpp/src/parquet/file_reader.cc @@ -187,6 +187,11 @@ ::arrow::io::ReadRange ComputeColumnChunkRange(FileMetaData* file_metadata, col_start > column_metadata->dictionary_page_offset()) { col_start = column_metadata->dictionary_page_offset(); } + if (column_metadata->has_symbol_table_page() && + column_metadata->symbol_table_page_offset() > 0 && + (col_start <= 0 || col_start > column_metadata->symbol_table_page_offset())) { + col_start = column_metadata->symbol_table_page_offset(); + } int64_t col_length = column_metadata->total_compressed_size(); int64_t col_end; @@ -282,7 +287,7 @@ class SerializedRowGroup : public RowGroupReader::Contents { throw ParquetException("Encrypted files cannot contain more than 32767 columns"); } - CryptoContext ctx{col->has_dictionary_page(), + CryptoContext ctx{col->has_dictionary_page() || col->has_symbol_table_page(), static_cast(row_group_ordinal_), static_cast(i), std::move(meta_decryptor_factory), std::move(data_decryptor_factory)}; diff --git a/cpp/src/parquet/file_serialize_test.cc b/cpp/src/parquet/file_serialize_test.cc index f287e493a908..15dc4c1265a3 100644 --- a/cpp/src/parquet/file_serialize_test.cc +++ b/cpp/src/parquet/file_serialize_test.cc @@ -440,6 +440,112 @@ TEST(TestBufferedRowGroupWriter, MultiPageDisabledDictionary) { } } +TEST(TestBufferedRowGroupWriter, FsstSymbolTablePage) { + constexpr int kValueCount = 1000; + auto sink = CreateOutputStream(); + auto writer_props = parquet::WriterProperties::Builder() + .disable_dictionary() + ->encoding(Encoding::FSST) + ->data_pagesize(512) + ->enable_page_checksum() +#ifdef ARROW_WITH_ZSTD + ->compression(Compression::ZSTD) +#endif + ->build(); + schema::NodeVector fields; + fields.push_back(PrimitiveNode::Make("col", Repetition::REQUIRED, Type::BYTE_ARRAY)); + auto schema = std::static_pointer_cast( + GroupNode::Make("schema", Repetition::REQUIRED, fields)); + auto file_writer = ParquetFileWriter::Open(sink, schema, writer_props); + auto rg_writer = file_writer->AppendBufferedRowGroup(); + auto col_writer = static_cast(rg_writer->column(0)); + std::vector strings; + std::vector values; + strings.reserve(kValueCount); + values.reserve(kValueCount); + for (int i = 0; i < kValueCount; ++i) { + strings.push_back("https://arrow.apache.org/parquet/fsst/record/" + + std::to_string(i % 23)); + values.emplace_back(strings.back()); + } + col_writer->WriteBatch(kValueCount, nullptr, nullptr, values.data()); + rg_writer->Close(); + file_writer->Close(); + PARQUET_ASSIGN_OR_THROW(auto buffer, sink->Finish()); + + auto source = std::make_shared<::arrow::io::BufferReader>(buffer); + auto file_reader = ParquetFileReader::Open(source); + auto column_metadata = file_reader->metadata()->RowGroup(0)->ColumnChunk(0); + ASSERT_TRUE(column_metadata->has_symbol_table_page()); + ASSERT_GT(column_metadata->symbol_table_page_offset(), 0); + ASSERT_GT(column_metadata->symbol_table_page_length(), 0); + ASSERT_LT(column_metadata->symbol_table_page_offset(), + column_metadata->data_page_offset()); + + auto rg_reader = file_reader->RowGroup(0); + auto byte_array_reader = + std::static_pointer_cast(rg_reader->Column(0)); + std::vector actual(kValueCount); + int64_t values_read = 0; + ASSERT_EQ(byte_array_reader->ReadBatch(kValueCount, nullptr, nullptr, actual.data(), + &values_read), + kValueCount); + ASSERT_EQ(values_read, kValueCount); + for (int i = 0; i < kValueCount; ++i) { + ASSERT_EQ(actual[i], values[i]); + } + + auto page_buffer = + ::arrow::SliceBuffer(buffer, column_metadata->symbol_table_page_offset(), + column_metadata->total_compressed_size()); + auto page_source = std::make_shared<::arrow::io::BufferReader>(page_buffer); + ReaderProperties reader_properties; + reader_properties.set_page_checksum_verification(true); + auto page_reader = + PageReader::Open(page_source, kValueCount, +#ifdef ARROW_WITH_ZSTD + Compression::ZSTD, +#else + Compression::UNCOMPRESSED, +#endif + reader_properties, *file_reader->metadata()->schema()->Column(0)); + auto page = page_reader->NextPage(); + ASSERT_NE(page, nullptr); + ASSERT_EQ(page->type(), PageType::SYMBOL_TABLE_PAGE); + auto* symbol_table_page = static_cast(page.get()); + ASSERT_EQ(symbol_table_page->symbol_table_type(), SymbolTableType::FSST); + ASSERT_GE(symbol_table_page->size(), 9); + ASSERT_LE(symbol_table_page->size(), 2049); +} + +TEST(TestBufferedRowGroupWriter, EmptyFsstColumnWritesSymbolTablePage) { + auto sink = CreateOutputStream(); + auto writer_props = parquet::WriterProperties::Builder() + .disable_dictionary() + ->encoding(Encoding::FSST) + ->build(); + schema::NodeVector fields; + fields.push_back(PrimitiveNode::Make("col", Repetition::REQUIRED, Type::BYTE_ARRAY)); + auto schema = std::static_pointer_cast( + GroupNode::Make("schema", Repetition::REQUIRED, fields)); + auto file_writer = ParquetFileWriter::Open(sink, schema, writer_props); + auto rg_writer = file_writer->AppendBufferedRowGroup(); + static_cast(rg_writer->column(0))->Close(); + rg_writer->Close(); + file_writer->Close(); + PARQUET_ASSIGN_OR_THROW(auto buffer, sink->Finish()); + + auto source = std::make_shared<::arrow::io::BufferReader>(buffer); + auto file_reader = ParquetFileReader::Open(source); + auto row_group = file_reader->metadata()->RowGroup(0); + ASSERT_EQ(row_group->num_rows(), 0); + auto column_metadata = row_group->ColumnChunk(0); + ASSERT_TRUE(column_metadata->has_symbol_table_page()); + ASSERT_GT(column_metadata->symbol_table_page_offset(), 0); + ASSERT_GT(column_metadata->symbol_table_page_length(), 0); + ASSERT_EQ(row_group->file_offset(), column_metadata->symbol_table_page_offset()); +} + TEST(ParquetRoundtrip, AllNulls) { auto primitive_node = PrimitiveNode::Make("nulls", Repetition::OPTIONAL, nullptr, Type::INT32); diff --git a/cpp/src/parquet/fsst_internal.cc b/cpp/src/parquet/fsst_internal.cc new file mode 100644 index 000000000000..0222032fef8f --- /dev/null +++ b/cpp/src/parquet/fsst_internal.cc @@ -0,0 +1,346 @@ +// 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. + +#include "parquet/fsst_internal.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "arrow/buffer.h" +#include "arrow/result.h" +#include "arrow/vendored/fsst/fsst.h" + +#include "parquet/exception.h" + +namespace parquet::internal { +namespace { + +constexpr uint8_t kFsstEscape = 0xFF; + +} // namespace + +struct FsstSymbolTable::CwiState { + CwiState() { cwi_to_parquet_code.fill(kFsstEscape); } + + ~CwiState() { + if (encoder != nullptr) { + fsst_destroy(encoder); + } + } + + fsst_encoder_t* encoder = nullptr; + fsst_decoder_t decoder{}; + std::array cwi_to_parquet_code; +}; + +FsstSymbolTable::~FsstSymbolTable() = default; + +FsstSymbolTable::FsstSymbolTable(std::vector symbols) + : symbols_(std::move(symbols)), cwi_(std::make_unique()) { + InitializeCwiDecoder(); +} + +std::shared_ptr FsstSymbolTable::Train( + const std::vector& values) { + std::vector lengths; + std::vector inputs; + if (values.empty()) { + // CWI expects at least one input slot. A zero-length sample produces a + // fully usable table for encoding values on later pages. + static constexpr unsigned char kEmptyInput = 0; + lengths.push_back(0); + inputs.push_back(&kEmptyInput); + } else { + lengths.reserve(values.size()); + inputs.reserve(values.size()); + for (const std::string& value : values) { + lengths.push_back(value.size()); + inputs.push_back(reinterpret_cast(value.data())); + } + } + + auto trained = std::make_unique(); + trained->encoder = fsst_create(lengths.size(), lengths.data(), inputs.data(), + /*zeroTerminated=*/0); + if (trained->encoder == nullptr) { + throw ParquetException("CWI FSST failed to train a symbol table"); + } + const fsst_decoder_t native_decoder = fsst_decoder(trained->encoder); + + // The pinned CWI format stores nSymbols in byte 1 of its native export. + // We use it only to inspect the trained table; native bytes are never written + // to Parquet. + std::array native_table{}; + const unsigned int native_size = fsst_export(trained->encoder, native_table.data()); + if (native_size < 17 || native_table[0] != 1) { + throw ParquetException("CWI FSST produced an invalid native symbol table"); + } + const uint16_t symbol_count = native_table[1]; + uint16_t histogram_count = 0; + for (int length = 0; length < 8; ++length) { + histogram_count += native_table[9 + length]; + } + if (histogram_count != symbol_count) { + throw ParquetException("CWI FSST produced an inconsistent native symbol table"); + } + + std::vector native_codes(symbol_count); + std::iota(native_codes.begin(), native_codes.end(), 0); + std::stable_sort(native_codes.begin(), native_codes.end(), + [&](uint16_t left, uint16_t right) { + return native_decoder.len[left] < native_decoder.len[right]; + }); + + std::vector symbols; + symbols.reserve(symbol_count); + for (uint16_t parquet_code = 0; parquet_code < symbol_count; ++parquet_code) { + const uint16_t native_code = native_codes[parquet_code]; + const uint8_t length = native_decoder.len[native_code]; + if (length == 0 || length > 8) { + throw ParquetException("CWI FSST produced an invalid symbol length"); + } + trained->cwi_to_parquet_code[native_code] = static_cast(parquet_code); + symbols.emplace_back( + reinterpret_cast(&native_decoder.symbol[native_code]), length); + } + + auto table = std::shared_ptr(new FsstSymbolTable(std::move(symbols))); + table->cwi_->encoder = trained->encoder; + trained->encoder = nullptr; + table->cwi_->cwi_to_parquet_code = trained->cwi_to_parquet_code; + return table; +} + +std::shared_ptr FsstSymbolTable::Deserialize( + const std::shared_ptr<::arrow::Buffer>& body) { + constexpr int max_length = 8; + constexpr int64_t fixed_size = 9; + if (body == nullptr || body->size() < fixed_size || body->size() > 2049) { + throw ParquetException("Invalid FSST symbol table body size: ", + body == nullptr ? -1 : body->size()); + } + + const uint8_t* data = body->data(); + const uint32_t symbol_count = data[0]; + + std::vector histogram(max_length); + uint64_t histogram_sum = 0; + uint64_t expected_symbol_bytes = 0; + const uint8_t* histogram_data = data + 1; + for (int i = 0; i < max_length; ++i) { + histogram[i] = histogram_data[i]; + histogram_sum += histogram[i]; + expected_symbol_bytes += static_cast(histogram[i]) * (i + 1); + } + if (histogram_sum != symbol_count) { + throw ParquetException("FSST length histogram does not match symbol count"); + } + if (expected_symbol_bytes != static_cast(body->size() - fixed_size)) { + throw ParquetException("FSST symbol data size does not match length histogram"); + } + + std::vector symbols; + symbols.reserve(symbol_count); + const char* symbol_data = reinterpret_cast(data + fixed_size); + int64_t offset = 0; + for (int length = 1; length <= max_length; ++length) { + for (uint32_t i = 0; i < histogram[length - 1]; ++i) { + symbols.emplace_back(symbol_data + offset, length); + offset += length; + } + } + return std::shared_ptr(new FsstSymbolTable(std::move(symbols))); +} + +std::shared_ptr<::arrow::Buffer> FsstSymbolTable::Serialize( + ::arrow::MemoryPool* pool) const { + constexpr int max_length = 8; + constexpr int64_t fixed_size = 9; + int64_t symbol_bytes = 0; + std::vector histogram(max_length, 0); + for (const std::string& symbol : symbols_) { + ++histogram[symbol.size() - 1]; + symbol_bytes += static_cast(symbol.size()); + } + + auto buffer = ::arrow::AllocateBuffer(fixed_size + symbol_bytes, pool).ValueOrDie(); + uint8_t* output = buffer->mutable_data(); + output[0] = static_cast(symbols_.size()); + uint8_t* histogram_output = output + 1; + for (int i = 0; i < max_length; ++i) { + histogram_output[i] = static_cast(histogram[i]); + } + uint8_t* symbol_output = output + fixed_size; + for (const std::string& symbol : symbols_) { + std::copy(symbol.begin(), symbol.end(), symbol_output); + symbol_output += symbol.size(); + } + return std::shared_ptr<::arrow::Buffer>(std::move(buffer)); +} + +void FsstSymbolTable::InitializeCwiDecoder() { + std::memset(&cwi_->decoder, 0, sizeof(cwi_->decoder)); + cwi_->decoder.zeroTerminated = 0; + for (size_t code = 0; code < symbols_.size(); ++code) { + const std::string& symbol = symbols_[code]; + if (symbol.empty() || symbol.size() > 8) { + throw ParquetException("Invalid FSST8 symbol length"); + } + cwi_->decoder.len[code] = static_cast(symbol.size()); + std::memcpy(reinterpret_cast(&cwi_->decoder.symbol[code]), symbol.data(), + symbol.size()); + } +} + +bool FsstSymbolTable::CompressBatch(const std::vector& input, + size_t max_output_size, + std::vector* end_offsets, + std::vector* output) const { + end_offsets->clear(); + output->clear(); + if (input.empty()) { + return true; + } + if (cwi_->encoder == nullptr) { + throw ParquetException("FSST compression requires a CWI-trained FSST8 table"); + } + + std::vector input_lengths; + std::vector input_pointers; + input_lengths.reserve(input.size()); + input_pointers.reserve(input.size()); + size_t scratch_size = 0; + for (const std::string& value : input) { + if (scratch_size > std::numeric_limits::max() - 7 || + value.size() > (std::numeric_limits::max() - scratch_size - 7) / 2) { + throw ParquetException("FSST compression scratch size overflow"); + } + scratch_size += 7 + 2 * value.size(); + input_lengths.push_back(value.size()); + input_pointers.push_back(reinterpret_cast(value.data())); + } + + std::vector scratch(scratch_size); + std::vector compressed_lengths(input.size()); + std::vector compressed_pointers(input.size()); + const size_t compressed_count = + fsst_compress(cwi_->encoder, input.size(), input_lengths.data(), + input_pointers.data(), scratch.size(), scratch.data(), + compressed_lengths.data(), compressed_pointers.data()); + if (compressed_count != input.size()) { + throw ParquetException("CWI FSST did not compress the complete input batch"); + } + + end_offsets->reserve(input.size()); + output->reserve(std::min(max_output_size, scratch_size)); + const uint8_t* scratch_end = scratch.data() + scratch.size(); + for (size_t value_index = 0; value_index < input.size(); ++value_index) { + const uint8_t* compressed = compressed_pointers[value_index]; + const size_t compressed_size = compressed_lengths[value_index]; + if (compressed == nullptr || compressed < scratch.data() || + compressed > scratch_end || + compressed_size > static_cast(scratch_end - compressed)) { + throw ParquetException("CWI FSST returned an invalid output range"); + } + + size_t position = 0; + while (position < compressed_size) { + const uint8_t native_code = compressed[position++]; + if (native_code == kFsstEscape) { + if (position == compressed_size) { + throw ParquetException("CWI FSST produced a truncated escape sequence"); + } + if (output->size() > max_output_size || max_output_size - output->size() < 2) { + end_offsets->clear(); + output->clear(); + return false; + } + output->push_back(kFsstEscape); + output->push_back(compressed[position++]); + } else { + const uint8_t parquet_code = cwi_->cwi_to_parquet_code[native_code]; + if (parquet_code == kFsstEscape) { + throw ParquetException("CWI FSST emitted an unknown symbol code"); + } + if (output->size() == max_output_size) { + end_offsets->clear(); + output->clear(); + return false; + } + output->push_back(parquet_code); + } + } + if (output->size() > static_cast(std::numeric_limits::max())) { + throw ParquetException("FSST compressed data exceeds INT32_MAX"); + } + end_offsets->push_back(static_cast(output->size())); + } + return true; +} + +bool FsstSymbolTable::Compress(std::string_view input, size_t max_output_size, + std::vector* output) const { + std::vector end_offsets; + return CompressBatch({std::string(input)}, max_output_size, &end_offsets, output); +} + +void FsstSymbolTable::Decompress(const uint8_t* input, int64_t input_size, + std::vector* output) const { + output->clear(); + if (input_size < 0 || (input == nullptr && input_size != 0)) { + throw ParquetException("Invalid FSST compressed value buffer"); + } + + size_t decoded_size = 0; + int64_t position = 0; + while (position < input_size) { + const uint8_t code = input[position++]; + size_t append_size; + if (code == kFsstEscape) { + if (position == input_size) { + throw ParquetException("FSST value ends with a truncated escape"); + } + ++position; + append_size = 1; + } else { + if (code >= symbols_.size()) { + throw ParquetException("FSST value references invalid symbol code ", code); + } + append_size = symbols_[code].size(); + } + if (decoded_size > + static_cast(std::numeric_limits::max()) - append_size) { + throw ParquetException("FSST decoded value exceeds INT32_MAX"); + } + decoded_size += append_size; + } + + output->resize(decoded_size); + const size_t actual_size = + fsst_decompress(&cwi_->decoder, static_cast(input_size), input, + output->size(), output->data()); + if (actual_size != decoded_size) { + throw ParquetException("CWI FSST decompression size mismatch"); + } +} + +} // namespace parquet::internal diff --git a/cpp/src/parquet/fsst_internal.h b/cpp/src/parquet/fsst_internal.h new file mode 100644 index 000000000000..0b96549db43e --- /dev/null +++ b/cpp/src/parquet/fsst_internal.h @@ -0,0 +1,69 @@ +// 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. + +#pragma once + +#include +#include +#include +#include +#include + +#include "arrow/buffer.h" +#include "arrow/memory_pool.h" + +#include "parquet/types.h" + +namespace parquet::internal { + +/// Parquet adapter around the vendored CWI FSST codec. +/// +/// The CWI library owns FSST8 training and compression. This class remaps its +/// native code order to the portable, length-ordered Parquet representation. +class PARQUET_EXPORT FsstSymbolTable { + public: + ~FsstSymbolTable(); + + static std::shared_ptr Train(const std::vector& values); + + static std::shared_ptr Deserialize( + const std::shared_ptr<::arrow::Buffer>& body); + + std::shared_ptr<::arrow::Buffer> Serialize(::arrow::MemoryPool* pool) const; + + bool CompressBatch(const std::vector& input, size_t max_output_size, + std::vector* end_offsets, + std::vector* output) const; + bool Compress(std::string_view input, size_t max_output_size, + std::vector* output) const; + void Decompress(const uint8_t* input, int64_t input_size, + std::vector* output) const; + + uint32_t symbol_count() const { return static_cast(symbols_.size()); } + const std::string& symbol(uint32_t code) const { return symbols_[code]; } + + private: + struct CwiState; + + explicit FsstSymbolTable(std::vector symbols); + void InitializeCwiDecoder(); + + std::vector symbols_; + std::unique_ptr cwi_; +}; + +} // namespace parquet::internal diff --git a/cpp/src/parquet/meson.build b/cpp/src/parquet/meson.build index f2aff7dfa1de..2d73b82bc401 100644 --- a/cpp/src/parquet/meson.build +++ b/cpp/src/parquet/meson.build @@ -39,6 +39,9 @@ parquet_srcs = files( 'exception.cc', 'file_reader.cc', 'file_writer.cc', + 'fsst_internal.cc', + '../arrow/vendored/fsst/libfsst.cpp', + '../arrow/vendored/fsst/fsst_avx512.cpp', 'geospatial/statistics.cc', 'geospatial/util_internal.cc', 'geospatial/util_json_internal.cc', diff --git a/cpp/src/parquet/metadata.cc b/cpp/src/parquet/metadata.cc index 98f60df63dd4..e71f2d20c797 100644 --- a/cpp/src/parquet/metadata.cc +++ b/cpp/src/parquet/metadata.cc @@ -416,6 +416,28 @@ class ColumnChunkMetaData::ColumnChunkMetaDataImpl { return column_metadata_->dictionary_page_offset; } + inline bool has_symbol_table_page() const { + if (column_metadata_->__isset.symbol_table_page_offset != + column_metadata_->__isset.symbol_table_page_length) { + throw ParquetException( + "Symbol table page metadata must contain both offset and length"); + } + if (column_metadata_->__isset.symbol_table_page_offset && + (column_metadata_->symbol_table_page_offset <= 0 || + column_metadata_->symbol_table_page_length <= 0)) { + throw ParquetException("Invalid symbol table page offset or length"); + } + return column_metadata_->__isset.symbol_table_page_offset; + } + + inline int64_t symbol_table_page_offset() const { + return column_metadata_->symbol_table_page_offset; + } + + inline int32_t symbol_table_page_length() const { + return column_metadata_->symbol_table_page_length; + } + inline int64_t data_page_offset() const { return column_metadata_->data_page_offset; } inline bool has_index_page() const { @@ -551,6 +573,18 @@ int64_t ColumnChunkMetaData::dictionary_page_offset() const { return impl_->dictionary_page_offset(); } +bool ColumnChunkMetaData::has_symbol_table_page() const { + return impl_->has_symbol_table_page(); +} + +int64_t ColumnChunkMetaData::symbol_table_page_offset() const { + return impl_->symbol_table_page_offset(); +} + +int32_t ColumnChunkMetaData::symbol_table_page_length() const { + return impl_->symbol_table_page_length(); +} + int64_t ColumnChunkMetaData::data_page_offset() const { return impl_->data_page_offset(); } @@ -1678,6 +1712,7 @@ class ColumnChunkMetaDataBuilder::ColumnChunkMetaDataBuilderImpl { void Finish(int64_t num_values, int64_t dictionary_page_offset, int64_t index_page_offset, int64_t data_page_offset, + int64_t symbol_table_page_offset, int32_t symbol_table_page_length, int64_t compressed_size, int64_t uncompressed_size, bool has_dictionary, bool dictionary_fallback, const std::map& dict_encoding_stats, @@ -1686,6 +1721,10 @@ class ColumnChunkMetaDataBuilder::ColumnChunkMetaDataBuilderImpl { if (dictionary_page_offset > 0) { column_chunk_->meta_data.__set_dictionary_page_offset(dictionary_page_offset); } + if (symbol_table_page_offset > 0) { + column_chunk_->meta_data.__set_symbol_table_page_offset(symbol_table_page_offset); + column_chunk_->meta_data.__set_symbol_table_page_length(symbol_table_page_length); + } // The `file_offset` field is deprecated and should be set to 0. // See https://github.com/apache/parquet-format/pull/440 for detail. column_chunk_->__set_file_offset(0); @@ -1736,6 +1775,14 @@ class ColumnChunkMetaDataBuilder::ColumnChunkMetaDataBuilderImpl { thrift_encoding_stats.push_back(data_enc_stat); add_encoding(data_encoding); } + if (symbol_table_page_offset > 0) { + format::PageEncodingStats symbol_table_stat; + symbol_table_stat.__set_page_type(format::PageType::SYMBOL_TABLE_PAGE); + symbol_table_stat.__set_encoding(format::Encoding::FSST); + symbol_table_stat.__set_count(1); + thrift_encoding_stats.push_back(symbol_table_stat); + add_encoding(format::Encoding::FSST); + } column_chunk_->meta_data.__set_encodings(std::move(thrift_encodings)); column_chunk_->meta_data.__set_encoding_stats(std::move(thrift_encoding_stats)); @@ -1867,8 +1914,23 @@ void ColumnChunkMetaDataBuilder::Finish( const std::map& dict_encoding_stats, const std::map& data_encoding_stats, const std::shared_ptr& encryptor) { + Finish(num_values, dictionary_page_offset, index_page_offset, data_page_offset, + /*symbol_table_page_offset=*/0, /*symbol_table_page_length=*/0, compressed_size, + uncompressed_size, has_dictionary, dictionary_fallback, dict_encoding_stats, + data_encoding_stats, encryptor); +} + +void ColumnChunkMetaDataBuilder::Finish( + int64_t num_values, int64_t dictionary_page_offset, int64_t index_page_offset, + int64_t data_page_offset, int64_t symbol_table_page_offset, + int32_t symbol_table_page_length, int64_t compressed_size, int64_t uncompressed_size, + bool has_dictionary, bool dictionary_fallback, + const std::map& dict_encoding_stats, + const std::map& data_encoding_stats, + const std::shared_ptr& encryptor) { impl_->Finish(num_values, dictionary_page_offset, index_page_offset, data_page_offset, - compressed_size, uncompressed_size, has_dictionary, dictionary_fallback, + symbol_table_page_offset, symbol_table_page_length, compressed_size, + uncompressed_size, has_dictionary, dictionary_fallback, dict_encoding_stats, data_encoding_stats, encryptor); } @@ -1947,13 +2009,18 @@ class RowGroupMetaDataBuilder::RowGroupMetaDataBuilderImpl { if (i == 0) { const format::ColumnMetaData& first_col = row_group_->columns[0].meta_data; // As per spec, file_offset for the row group points to the first - // dictionary or data page of the column. + // dictionary, symbol table, or data page of the column. if (first_col.__isset.dictionary_page_offset && first_col.dictionary_page_offset > 0) { file_offset = first_col.dictionary_page_offset; } else { file_offset = first_col.data_page_offset; } + if (first_col.__isset.symbol_table_page_offset && + first_col.symbol_table_page_offset > 0 && + (file_offset <= 0 || first_col.symbol_table_page_offset < file_offset)) { + file_offset = first_col.symbol_table_page_offset; + } } // sometimes column metadata is encrypted and not available to read, // so we must get total_compressed_size from column builder diff --git a/cpp/src/parquet/metadata.h b/cpp/src/parquet/metadata.h index 5db4905beecd..9c8f0dd4eca9 100644 --- a/cpp/src/parquet/metadata.h +++ b/cpp/src/parquet/metadata.h @@ -150,6 +150,9 @@ class PARQUET_EXPORT ColumnChunkMetaData { std::optional bloom_filter_length() const; bool has_dictionary_page() const; int64_t dictionary_page_offset() const; + bool has_symbol_table_page() const; + int64_t symbol_table_page_offset() const; + int32_t symbol_table_page_length() const; int64_t data_page_offset() const; bool has_index_page() const; int64_t index_page_offset() const; @@ -451,6 +454,8 @@ class PARQUET_EXPORT ColumnChunkMetaDataBuilder { int64_t total_compressed_size() const; // commit the metadata + // Kept for source and binary compatibility with page writers that do not + // produce symbol table pages. void Finish(int64_t num_values, int64_t dictionary_page_offset, int64_t index_page_offset, int64_t data_page_offset, int64_t compressed_size, int64_t uncompressed_size, bool has_dictionary, @@ -459,6 +464,15 @@ class PARQUET_EXPORT ColumnChunkMetaDataBuilder { const std::map& data_encoding_stats_, const std::shared_ptr& encryptor = NULLPTR); + void Finish(int64_t num_values, int64_t dictionary_page_offset, + int64_t index_page_offset, int64_t data_page_offset, + int64_t symbol_table_page_offset, int32_t symbol_table_page_length, + int64_t compressed_size, int64_t uncompressed_size, bool has_dictionary, + bool dictionary_fallback, + const std::map& dict_encoding_stats_, + const std::map& data_encoding_stats_, + const std::shared_ptr& encryptor = NULLPTR); + // The metadata contents, suitable for passing to ColumnChunkMetaData::Make const void* contents() const; diff --git a/cpp/src/parquet/metadata_test.cc b/cpp/src/parquet/metadata_test.cc index ac45be1fac38..0c4fecb12eca 100644 --- a/cpp/src/parquet/metadata_test.cc +++ b/cpp/src/parquet/metadata_test.cc @@ -51,10 +51,10 @@ std::unique_ptr GenerateTableMetaData( col1_builder->SetStatistics(stats_int); stats_float.set_is_signed(true); col2_builder->SetStatistics(stats_float); - col1_builder->Finish(nrows / 2, 4, 0, 10, 512, 600, true, false, dict_encoding_stats, - data_encoding_stats); - col2_builder->Finish(nrows / 2, 24, 0, 30, 512, 600, true, false, dict_encoding_stats, - data_encoding_stats); + col1_builder->Finish(nrows / 2, 4, 0, 10, 0, 0, 512, 600, true, false, + dict_encoding_stats, data_encoding_stats); + col2_builder->Finish(nrows / 2, 24, 0, 30, 0, 0, 512, 600, true, false, + dict_encoding_stats, data_encoding_stats); rg1_builder->set_num_rows(nrows / 2); rg1_builder->Finish(1024); @@ -69,8 +69,8 @@ std::unique_ptr GenerateTableMetaData( col1_builder->Finish(nrows / 2, /*dictionary_page_offset=*/0, 0, 10, 512, 600, /*has_dictionary=*/false, false, dict_encoding_stats, data_encoding_stats); - col2_builder->Finish(nrows / 2, 16, 0, 26, 512, 600, true, false, dict_encoding_stats, - data_encoding_stats); + col2_builder->Finish(nrows / 2, 16, 0, 26, 0, 0, 512, 600, true, false, + dict_encoding_stats, data_encoding_stats); rg2_builder->set_num_rows(nrows / 2); rg2_builder->Finish(1024); diff --git a/cpp/src/parquet/parquet.thrift b/cpp/src/parquet/parquet.thrift index 9603cefed388..9d544e8f817c 100644 --- a/cpp/src/parquet/parquet.thrift +++ b/cpp/src/parquet/parquet.thrift @@ -637,6 +637,12 @@ enum Encoding { Support for INT32, INT64 and FIXED_LEN_BYTE_ARRAY added in 2.11. */ BYTE_STREAM_SPLIT = 9; + + /** Adaptive Lossless floating-Point encoding. Reserved by the Parquet format. */ + ALP = 10; + + /** Fast Static Symbol Table encoding for BYTE_ARRAY values. */ + FSST = 11; } /** @@ -664,6 +670,12 @@ enum PageType { INDEX_PAGE = 1; DICTIONARY_PAGE = 2; DATA_PAGE_V2 = 3; + SYMBOL_TABLE_PAGE = 4; +} + +/** The representation used by a symbol table page. */ +enum SymbolTableType { + FSST = 0; } /** @@ -808,6 +820,15 @@ struct BloomFilterHeader { 4: required BloomFilterCompression compression; } +/** Header for a shared symbol table page. */ +struct SymbolTablePageHeader { + /** The representation of the symbol table and its encoded values. */ + 1: required SymbolTableType type; + + /** Whether the page body is compressed with the column chunk codec. */ + 2: required bool is_compressed; +} + struct PageHeader { /** the type of the page: indicates which of the *_header fields is set **/ 1: required PageType type @@ -842,6 +863,7 @@ struct PageHeader { 6: optional IndexPageHeader index_page_header; 7: optional DictionaryPageHeader dictionary_page_header; 8: optional DataPageHeaderV2 data_page_header_v2; + 9: optional SymbolTablePageHeader symbol_table_page_header; } /** @@ -951,6 +973,12 @@ struct ColumnMetaData { /** Optional statistics specific for Geometry and Geography logical types */ 17: optional GeospatialStatistics geospatial_statistics; + + /** Byte offset from the beginning of the file to the symbol table page. */ + 18: optional i64 symbol_table_page_offset; + + /** Serialized symbol table page length, including its page header. */ + 19: optional i32 symbol_table_page_length; } struct EncryptionWithFooterKey { diff --git a/cpp/src/parquet/properties.cc b/cpp/src/parquet/properties.cc index 6e3cfddd7bb8..858258c670b8 100644 --- a/cpp/src/parquet/properties.cc +++ b/cpp/src/parquet/properties.cc @@ -103,6 +103,11 @@ void WriterProperties::Builder::CopyColumnSpecificProperties( this->encoding(col_path, col_props.encoding()); } + if (col_props.fsst_offset_encoding() != + default_column_properties_.fsst_offset_encoding()) { + this->fsst_offset_encoding(col_path, col_props.fsst_offset_encoding()); + } + if (col_props.bloom_filter_options().has_value()) { this->enable_bloom_filter(col_path, col_props.bloom_filter_options().value()); } diff --git a/cpp/src/parquet/properties.h b/cpp/src/parquet/properties.h index e2244a1176e3..e66b5b97fb3b 100644 --- a/cpp/src/parquet/properties.h +++ b/cpp/src/parquet/properties.h @@ -240,6 +240,14 @@ class PARQUET_EXPORT ColumnProperties { void set_encoding(Encoding::type encoding) { encoding_ = encoding; } + void set_fsst_offset_encoding(FsstOffsetEncoding::type encoding) { + if (encoding != FsstOffsetEncoding::PLAIN && + encoding != FsstOffsetEncoding::DELTA_BINARY_PACKED) { + throw ParquetException("Unsupported FSST offset encoding"); + } + fsst_offset_encoding_ = encoding; + } + void set_compression(Compression::type codec) { codec_ = codec; } void set_dictionary_enabled(bool dictionary_enabled) { @@ -284,6 +292,8 @@ class PARQUET_EXPORT ColumnProperties { Encoding::type encoding() const { return encoding_; } + FsstOffsetEncoding::type fsst_offset_encoding() const { return fsst_offset_encoding_; } + Compression::type compression() const { return codec_; } bool dictionary_enabled() const { return dictionary_enabled_; } @@ -311,6 +321,7 @@ class PARQUET_EXPORT ColumnProperties { private: Encoding::type encoding_; + FsstOffsetEncoding::type fsst_offset_encoding_ = FsstOffsetEncoding::PLAIN; Compression::type codec_; bool dictionary_enabled_; bool statistics_enabled_; @@ -572,6 +583,27 @@ class PARQUET_EXPORT WriterProperties { return this->encoding(path->ToDotString(), encoding_type); } + /// Select PLAIN or DELTA_BINARY_PACKED end offsets for FSST data pages. + Builder* fsst_offset_encoding(FsstOffsetEncoding::type encoding) { + default_column_properties_.set_fsst_offset_encoding(encoding); + return this; + } + + Builder* fsst_offset_encoding(const std::string& path, + FsstOffsetEncoding::type encoding) { + if (encoding != FsstOffsetEncoding::PLAIN && + encoding != FsstOffsetEncoding::DELTA_BINARY_PACKED) { + throw ParquetException("Unsupported FSST offset encoding"); + } + fsst_offset_encodings_[path] = encoding; + return this; + } + + Builder* fsst_offset_encoding(const std::shared_ptr& path, + FsstOffsetEncoding::type encoding) { + return fsst_offset_encoding(path->ToDotString(), encoding); + } + /// Specify compression codec in general for all columns. /// Default UNCOMPRESSED. Builder* compression(Compression::type codec) { @@ -877,6 +909,8 @@ class PARQUET_EXPORT WriterProperties { }; for (const auto& item : encodings_) get(item.first).set_encoding(item.second); + for (const auto& item : fsst_offset_encodings_) + get(item.first).set_fsst_offset_encoding(item.second); for (const auto& item : codecs_) get(item.first).set_compression(item.second); for (const auto& item : codec_options_) get(item.first).set_codec_options(item.second); @@ -930,6 +964,7 @@ class PARQUET_EXPORT WriterProperties { // Settings used for each column unless overridden in any of the maps below ColumnProperties default_column_properties_; std::unordered_map encodings_; + std::unordered_map fsst_offset_encodings_; std::unordered_map codecs_; std::unordered_map> codec_options_; std::unordered_map dictionary_enabled_; @@ -1003,6 +1038,11 @@ class PARQUET_EXPORT WriterProperties { return column_properties(path).encoding(); } + FsstOffsetEncoding::type fsst_offset_encoding( + const std::shared_ptr& path) const { + return column_properties(path).fsst_offset_encoding(); + } + Compression::type compression(const std::shared_ptr& path) const { return column_properties(path).compression(); } diff --git a/cpp/src/parquet/properties_test.cc b/cpp/src/parquet/properties_test.cc index 324ea2026a9e..58da9c5ea8b4 100644 --- a/cpp/src/parquet/properties_test.cc +++ b/cpp/src/parquet/properties_test.cc @@ -52,6 +52,8 @@ TEST(TestWriterProperties, Basics) { ASSERT_EQ(ParquetVersion::PARQUET_2_6, props->version()); ASSERT_EQ(ParquetDataPageVersion::V1, props->data_page_version()); ASSERT_FALSE(props->page_checksum_enabled()); + ASSERT_EQ(FsstOffsetEncoding::PLAIN, + props->fsst_offset_encoding(ColumnPath::FromDotString("any"))); } TEST(TestWriterProperties, DefaultCompression) { @@ -314,6 +316,8 @@ TEST_P(WriterPropertiesTest, RoundTripThroughBuilder) { ASSERT_EQ(round_tripped_col.dictionary_enabled(), column_properties.dictionary_enabled()); ASSERT_EQ(round_tripped_col.encoding(), column_properties.encoding()); + ASSERT_EQ(round_tripped_col.fsst_offset_encoding(), + column_properties.fsst_offset_encoding()); ASSERT_EQ(round_tripped_col.max_statistics_size(), column_properties.max_statistics_size()); ASSERT_EQ(round_tripped_col.page_index_enabled(), @@ -391,6 +395,12 @@ std::vector writer_properties_test_cases() { builder.encoding(column_a, Encoding::BYTE_STREAM_SPLIT); test_cases.emplace_back(builder.build(), "encoding_column_override"); } + { + WriterProperties::Builder builder; + builder.fsst_offset_encoding(FsstOffsetEncoding::DELTA_BINARY_PACKED); + builder.fsst_offset_encoding(column_a, FsstOffsetEncoding::PLAIN); + test_cases.emplace_back(builder.build(), "fsst_column_override"); + } { WriterProperties::Builder builder; builder.disable_write_page_index(); diff --git a/cpp/src/parquet/thrift_internal.h b/cpp/src/parquet/thrift_internal.h index 971e6ccebc9d..f36eab66582d 100644 --- a/cpp/src/parquet/thrift_internal.h +++ b/cpp/src/parquet/thrift_internal.h @@ -79,6 +79,10 @@ constexpr PageType::type FromThriftUnsafe(format::PageType::type type) { return static_cast(type); } +constexpr SymbolTableType::type FromThriftUnsafe(format::SymbolTableType::type type) { + return static_cast(type); +} + constexpr Compression::type FromThriftUnsafe(format::CompressionCodec::type type) { switch (type) { case format::CompressionCodec::UNCOMPRESSED: @@ -156,6 +160,11 @@ struct ThriftEnumTypeTraits<::parquet::format::PageType::type> { using ParquetEnum = PageType; }; +template <> +struct ThriftEnumTypeTraits<::parquet::format::SymbolTableType::type> { + using ParquetEnum = SymbolTableType; +}; + template <> struct ThriftEnumTypeTraits<::parquet::format::BoundaryOrder::type> { using ParquetEnum = BoundaryOrder; @@ -419,6 +428,10 @@ constexpr format::Encoding::type ToThrift(Encoding::type type) { return static_cast(type); } +constexpr format::SymbolTableType::type ToThrift(SymbolTableType::type type) { + return static_cast(type); +} + constexpr format::CompressionCodec::type ToThrift(Compression::type type) { switch (type) { case Compression::UNCOMPRESSED: diff --git a/cpp/src/parquet/types.cc b/cpp/src/parquet/types.cc index cc3199f367af..73efa0622282 100644 --- a/cpp/src/parquet/types.cc +++ b/cpp/src/parquet/types.cc @@ -96,6 +96,7 @@ bool PageCanUseChecksum(PageType::type pageType) { case PageType::type::DATA_PAGE: case PageType::type::DATA_PAGE_V2: case PageType::type::DICTIONARY_PAGE: + case PageType::type::SYMBOL_TABLE_PAGE: return true; default: return false; @@ -267,6 +268,10 @@ std::string EncodingToString(Encoding::type t) { return "RLE_DICTIONARY"; case Encoding::BYTE_STREAM_SPLIT: return "BYTE_STREAM_SPLIT"; + case Encoding::ALP: + return "ALP"; + case Encoding::FSST: + return "FSST"; default: return "UNKNOWN"; } diff --git a/cpp/src/parquet/types.h b/cpp/src/parquet/types.h index 687353aa9bcb..ef22035dde00 100644 --- a/cpp/src/parquet/types.h +++ b/cpp/src/parquet/types.h @@ -539,8 +539,10 @@ struct Encoding { DELTA_BYTE_ARRAY = 7, RLE_DICTIONARY = 8, BYTE_STREAM_SPLIT = 9, + ALP = 10, + FSST = 11, // Should always be last element (except UNKNOWN) - UNDEFINED = 10, + UNDEFINED = 12, UNKNOWN = 999 }; }; @@ -590,11 +592,26 @@ struct PageType { INDEX_PAGE, DICTIONARY_PAGE, DATA_PAGE_V2, + SYMBOL_TABLE_PAGE, // Should always be last element UNDEFINED }; }; +// Mirrors parquet::SymbolTableType +struct SymbolTableType { + enum type { + FSST = 0, + // Should always be last element + UNDEFINED = 1 + }; +}; + +// Values stored in the one-byte FSST data page header. +struct FsstOffsetEncoding { + enum type { PLAIN = 0, DELTA_BINARY_PACKED = 1, UNDEFINED = 2 }; +}; + bool PageCanUseChecksum(PageType::type pageType); class PARQUET_EXPORT ColumnOrder { diff --git a/docs/source/cpp/parquet.rst b/docs/source/cpp/parquet.rst index 1aace68626c9..16c5f7ae7fc6 100644 --- a/docs/source/cpp/parquet.rst +++ b/docs/source/cpp/parquet.rst @@ -407,6 +407,8 @@ Encodings +--------------------------+----------+----------+---------+ | DELTA_LENGTH_BYTE_ARRAY | ✓ | ✓ | | +--------------------------+----------+----------+---------+ +| FSST | ✓ | ✓ | \(3) | ++--------------------------+----------+----------+---------+ * \(1) Only supported for encoding definition and repetition levels, and boolean values. @@ -414,6 +416,11 @@ Encodings * \(2) On the write path, RLE_DICTIONARY is only enabled if Parquet format version 2.4 or greater is selected in :func:`WriterProperties::version`. +* \(3) Supported for BYTE_ARRAY columns. Disable dictionary encoding and select + ``Encoding::FSST``. Writers use the vendored CWI FSST8 codec and can choose + PLAIN or DELTA_BINARY_PACKED end offsets with ``fsst_offset_encoding()``. + The initial implementation supports FSST8 symbol tables. + Types ----- diff --git a/python/pyarrow/_parquet.pyx b/python/pyarrow/_parquet.pyx index 932632a50410..f6819d75d3aa 100644 --- a/python/pyarrow/_parquet.pyx +++ b/python/pyarrow/_parquet.pyx @@ -607,7 +607,7 @@ cdef class ColumnChunkMetaData(_Weakrefable): Encodings used for column (tuple of str). One of 'PLAIN', 'BIT_PACKED', 'RLE', 'BYTE_STREAM_SPLIT', 'DELTA_BINARY_PACKED', - 'DELTA_LENGTH_BYTE_ARRAY', 'DELTA_BYTE_ARRAY'. + 'DELTA_LENGTH_BYTE_ARRAY', 'DELTA_BYTE_ARRAY', 'ALP', 'FSST'. """ return tuple(map(encoding_name_from_enum, self.metadata.encodings())) @@ -624,6 +624,25 @@ cdef class ColumnChunkMetaData(_Weakrefable): else: return None + @property + def has_symbol_table_page(self): + """Whether a shared FSST symbol table is present (bool).""" + return bool(self.metadata.has_symbol_table_page()) + + @property + def symbol_table_page_offset(self): + """Offset of the symbol table page, or None when absent (int).""" + if self.has_symbol_table_page: + return self.metadata.symbol_table_page_offset() + return None + + @property + def symbol_table_page_length(self): + """Serialized symbol table page length, or None when absent (int).""" + if self.has_symbol_table_page: + return self.metadata.symbol_table_page_length() + return None + @property def data_page_offset(self): """Offset of data page relative to beginning of the file (int).""" @@ -1509,6 +1528,8 @@ cdef encoding_name_from_enum(ParquetEncoding encoding_): ParquetEncoding_DELTA_BYTE_ARRAY: 'DELTA_BYTE_ARRAY', ParquetEncoding_RLE_DICTIONARY: 'RLE_DICTIONARY', ParquetEncoding_BYTE_STREAM_SPLIT: 'BYTE_STREAM_SPLIT', + ParquetEncoding_ALP: 'ALP', + ParquetEncoding_FSST: 'FSST', }.get(encoding_, 'UNKNOWN') @@ -1521,6 +1542,7 @@ cdef encoding_enum_from_name(str encoding_name): 'DELTA_BINARY_PACKED': ParquetEncoding_DELTA_BINARY_PACKED, 'DELTA_LENGTH_BYTE_ARRAY': ParquetEncoding_DELTA_LENGTH_BYTE_ARRAY, 'DELTA_BYTE_ARRAY': ParquetEncoding_DELTA_BYTE_ARRAY, + 'FSST': ParquetEncoding_FSST, 'RLE_DICTIONARY': 'dict', 'PLAIN_DICTIONARY': 'dict', }.get(encoding_name, None) diff --git a/python/pyarrow/includes/libparquet.pxd b/python/pyarrow/includes/libparquet.pxd index df353cc7805f..d8c598c3b13e 100644 --- a/python/pyarrow/includes/libparquet.pxd +++ b/python/pyarrow/includes/libparquet.pxd @@ -131,6 +131,8 @@ cdef extern from "parquet/api/schema.h" namespace "parquet" nogil: ParquetEncoding_RLE_DICTIONARY" parquet::Encoding::RLE_DICTIONARY" ParquetEncoding_BYTE_STREAM_SPLIT \ " parquet::Encoding::BYTE_STREAM_SPLIT" + ParquetEncoding_ALP" parquet::Encoding::ALP" + ParquetEncoding_FSST" parquet::Encoding::FSST" enum ParquetCompression" parquet::Compression::type": ParquetCompression_UNCOMPRESSED" parquet::Compression::UNCOMPRESSED" @@ -367,6 +369,9 @@ cdef extern from "parquet/api/reader.h" namespace "parquet" nogil: int64_t has_dictionary_page() const int64_t dictionary_page_offset() const + int64_t has_symbol_table_page() const + int64_t symbol_table_page_offset() const + int32_t symbol_table_page_length() const int64_t data_page_offset() const int64_t index_page_offset() const int64_t total_compressed_size() const diff --git a/python/pyarrow/parquet/core.py b/python/pyarrow/parquet/core.py index ff880fdcf52c..430998bea1c8 100644 --- a/python/pyarrow/parquet/core.py +++ b/python/pyarrow/parquet/core.py @@ -830,7 +830,8 @@ def _sanitize_table(table, new_schema, flavor): Can only be used when ``use_dictionary`` is set to False, and cannot be used in combination with ``use_byte_stream_split``. Currently supported values: {'PLAIN', 'BYTE_STREAM_SPLIT', - 'DELTA_BINARY_PACKED', 'DELTA_LENGTH_BYTE_ARRAY', 'DELTA_BYTE_ARRAY'}. + 'DELTA_BINARY_PACKED', 'DELTA_LENGTH_BYTE_ARRAY', 'DELTA_BYTE_ARRAY', + 'FSST'}. Certain encodings are only compatible with certain data types. Please refer to the encodings section of `Reading and writing Parquet files `_. diff --git a/python/pyarrow/tests/parquet/test_basic.py b/python/pyarrow/tests/parquet/test_basic.py index 20e3f51bb677..953f325bdfd8 100644 --- a/python/pyarrow/tests/parquet/test_basic.py +++ b/python/pyarrow/tests/parquet/test_basic.py @@ -503,6 +503,11 @@ def test_column_encoding(): 'c': "DELTA_BYTE_ARRAY", 'd': "DELTA_BYTE_ARRAY"}) + # Check "FSST" for variable-length byte columns. + _check_roundtrip(mixed_table, expected=mixed_table, + use_dictionary=False, + column_encoding={'c': "FSST"}) + # Check "RLE" for boolean columns. _check_roundtrip(mixed_table, expected=mixed_table, use_dictionary=False, @@ -528,6 +533,12 @@ def test_column_encoding(): 'b': "PLAIN", 'c': "PLAIN"}) + # FSST only supports variable-length byte columns. + with pytest.raises(OSError, match="FSST encoder only supports BYTE_ARRAY"): + _check_roundtrip(mixed_table, expected=mixed_table, + use_dictionary=False, + column_encoding={'b': "FSST"}) + # Try to pass "RLE_DICTIONARY". # This should throw an error as dictionary encoding is already used by # default and not supported to be specified as "fallback" encoding diff --git a/python/pyarrow/tests/parquet/test_metadata.py b/python/pyarrow/tests/parquet/test_metadata.py index 9eee70b125e5..2ed01d276fdc 100644 --- a/python/pyarrow/tests/parquet/test_metadata.py +++ b/python/pyarrow/tests/parquet/test_metadata.py @@ -136,6 +136,9 @@ def test_parquet_metadata_api(): assert set(col_meta.encodings) == {'PLAIN', 'RLE'} assert col_meta.has_dictionary_page is False assert col_meta.dictionary_page_offset is None + assert col_meta.has_symbol_table_page is False + assert col_meta.symbol_table_page_offset is None + assert col_meta.symbol_table_page_length is None assert col_meta.data_page_offset > 0 assert col_meta.total_compressed_size > 0 assert col_meta.total_uncompressed_size > 0 @@ -145,6 +148,24 @@ def test_parquet_metadata_api(): col_meta.index_page_offset +def test_fsst_column_metadata(): + table = pa.table({ + 'value': [ + f'https://arrow.apache.org/parquet/fsst/value/{i % 17}' + for i in range(500) + ] + }) + sink = io.BytesIO() + pq.write_table(table, sink, use_dictionary=False, column_encoding='FSST') + metadata = pq.ParquetFile(pa.BufferReader(sink.getvalue())).metadata + column = metadata.row_group(0).column(0) + assert column.has_symbol_table_page is True + assert column.symbol_table_page_offset > 0 + assert column.symbol_table_page_length > 0 + assert column.symbol_table_page_offset < column.data_page_offset + assert 'FSST' in column.encodings + + def test_parquet_metadata_lifetime(tempdir): # ARROW-6642 - ensure that chained access keeps parent objects alive table = pa.table({'a': [1, 2, 3]}) From 1808ebd1e848478b78d36020ee775fd1660c3cf4 Mon Sep 17 00:00:00 2001 From: Arnav Balyan Date: Sat, 15 Aug 2026 17:19:12 +0530 Subject: [PATCH 2/2] update --- .../parquet/arrow/arrow_reader_writer_test.cc | 61 ++--- cpp/src/parquet/column_reader.cc | 21 +- cpp/src/parquet/column_reader_test.cc | 66 ++++++ cpp/src/parquet/column_writer.cc | 210 +++++++++++++++--- cpp/src/parquet/decoder.cc | 2 +- cpp/src/parquet/encoder.cc | 112 ++++++++-- cpp/src/parquet/encoding.h | 17 +- cpp/src/parquet/encoding_test.cc | 59 +++++ cpp/src/parquet/file_serialize_test.cc | 70 ++++++ cpp/src/parquet/fsst_internal.cc | 42 +++- cpp/src/parquet/fsst_internal.h | 5 + cpp/src/parquet/properties.cc | 5 + cpp/src/parquet/properties.h | 44 ++++ cpp/src/parquet/properties_test.cc | 15 ++ docs/source/cpp/parquet.rst | 4 +- 15 files changed, 627 insertions(+), 106 deletions(-) diff --git a/cpp/src/parquet/arrow/arrow_reader_writer_test.cc b/cpp/src/parquet/arrow/arrow_reader_writer_test.cc index e6049a55ad5f..0da3bf9da82e 100644 --- a/cpp/src/parquet/arrow/arrow_reader_writer_test.cc +++ b/cpp/src/parquet/arrow/arrow_reader_writer_test.cc @@ -487,34 +487,39 @@ TEST(FsstEncoding, ArrowRoundTripAndMetadata) { for (auto offset_encoding : {FsstOffsetEncoding::PLAIN, FsstOffsetEncoding::DELTA_BINARY_PACKED}) { for (auto page_version : {ParquetDataPageVersion::V1, ParquetDataPageVersion::V2}) { - ARROW_SCOPED_TRACE("offset encoding = ", offset_encoding, - ", page version = ", static_cast(page_version)); - auto properties = WriterProperties::Builder() - .disable_dictionary() - ->encoding(Encoding::FSST) - ->fsst_offset_encoding(offset_encoding) - ->data_page_version(page_version) - ->data_pagesize(512) - ->enable_write_page_index() - ->build(); - - CheckConfiguredRoundtrip(table, nullptr, properties); - - ASSERT_OK_AND_ASSIGN(auto buffer, - WriteTableToBuffer(table, table->num_rows(), properties)); - auto reader = - ParquetFileReader::Open(std::make_shared<::arrow::io::BufferReader>(buffer)); - auto column = reader->metadata()->RowGroup(0)->ColumnChunk(0); - ASSERT_TRUE(column->has_symbol_table_page()); - ASSERT_GT(column->symbol_table_page_offset(), 0); - ASSERT_GT(column->symbol_table_page_length(), 0); - ASSERT_THAT(column->encodings(), ::testing::Contains(Encoding::FSST)); - ASSERT_THAT(column->encoding_stats(), - ::testing::Contains(::testing::AllOf( - ::testing::Field(&PageEncodingStats::page_type, - PageType::SYMBOL_TABLE_PAGE), - ::testing::Field(&PageEncodingStats::encoding, Encoding::FSST), - ::testing::Field(&PageEncodingStats::count, 1)))); + for (const int32_t training_pages : {1, 3, -1}) { + ARROW_SCOPED_TRACE("offset encoding = ", offset_encoding, + ", page version = ", static_cast(page_version), + ", training pages = ", training_pages); + auto properties = WriterProperties::Builder() + .disable_dictionary() + ->encoding(Encoding::FSST) + ->fsst_offset_encoding(offset_encoding) + ->fsst_training_data_pages(training_pages) + ->data_page_version(page_version) + ->data_pagesize(512) + ->enable_write_page_index() + ->build(); + + CheckConfiguredRoundtrip(table, nullptr, properties); + + ASSERT_OK_AND_ASSIGN(auto buffer, + WriteTableToBuffer(table, table->num_rows(), properties)); + auto reader = + ParquetFileReader::Open(std::make_shared<::arrow::io::BufferReader>(buffer)); + auto column = reader->metadata()->RowGroup(0)->ColumnChunk(0); + ASSERT_TRUE(column->has_symbol_table_page()); + ASSERT_GT(column->symbol_table_page_offset(), 0); + ASSERT_GT(column->symbol_table_page_length(), 0); + ASSERT_LT(column->symbol_table_page_offset(), column->data_page_offset()); + ASSERT_THAT(column->encodings(), ::testing::Contains(Encoding::FSST)); + ASSERT_THAT(column->encoding_stats(), + ::testing::Contains(::testing::AllOf( + ::testing::Field(&PageEncodingStats::page_type, + PageType::SYMBOL_TABLE_PAGE), + ::testing::Field(&PageEncodingStats::encoding, Encoding::FSST), + ::testing::Field(&PageEncodingStats::count, 1)))); + } } } diff --git a/cpp/src/parquet/column_reader.cc b/cpp/src/parquet/column_reader.cc index 27fda888c965..30a8e7552fb8 100644 --- a/cpp/src/parquet/column_reader.cc +++ b/cpp/src/parquet/column_reader.cc @@ -252,7 +252,7 @@ void CheckSymbolTablePageHeader(const format::PageHeader& page_header) { } const auto symbol_table_type = LoadEnumSafe(&page_header.symbol_table_page_header.type); if (symbol_table_type != SymbolTableType::FSST) { - throw ParquetException("Only FSST8 symbol tables are currently supported"); + throw ParquetException("Unsupported symbol table type"); } if (page_header.uncompressed_page_size < 9 || page_header.uncompressed_page_size > 2049) { @@ -867,10 +867,6 @@ class ColumnReaderImplBase { } void ConfigureDictionary(const DictionaryPage* page) { - if (fsst_symbol_table_body_ != nullptr) { - throw ParquetException( - "Dictionary and symbol table pages cannot coexist in a column chunk"); - } int encoding = static_cast(page->encoding()); if (page->encoding() == Encoding::PLAIN_DICTIONARY || page->encoding() == Encoding::PLAIN) { @@ -902,21 +898,16 @@ class ColumnReaderImplBase { } new_dictionary_ = true; - dictionary_configured_ = true; current_decoder_.SetDecoder(decoders_[encoding].get()); ARROW_DCHECK(current_decoder_); } void ConfigureSymbolTable(const SymbolTablePage* page) { - if (dictionary_configured_) { - throw ParquetException( - "Dictionary and symbol table pages cannot coexist in a column chunk"); - } if (fsst_symbol_table_body_ != nullptr) { - throw ParquetException("Column cannot have more than one symbol table page"); + throw ParquetException("Duplicate symbol table page"); } if (page->symbol_table_type() != SymbolTableType::FSST) { - throw ParquetException("Only FSST8 symbol tables are currently supported"); + throw ParquetException("Unsupported symbol table type"); } fsst_symbol_table_type_ = page->symbol_table_type(); PARQUET_ASSIGN_OR_THROW(fsst_symbol_table_body_, @@ -1042,10 +1033,6 @@ class ColumnReaderImplBase { if constexpr (!std::is_same_v) { throw ParquetException("FSST encoding only supports BYTE_ARRAY"); } else { - if (fsst_symbol_table_body_ == nullptr) { - throw ParquetException( - "FSST data page must be preceded by a symbol table page"); - } auto decoder = MakeFsstDecoder(descr_, fsst_symbol_table_type_, fsst_symbol_table_body_, pool_); auto typed_decoder = std::unique_ptr( @@ -1117,7 +1104,6 @@ class ColumnReaderImplBase { /// Flag to signal when a new dictionary has been set, for the benefit of /// DictionaryRecordReader bool new_dictionary_ = false; - bool dictionary_configured_ = false; // The exposed encoding ExposedEncoding exposed_encoding_ = ExposedEncoding::NO_ENCODING; @@ -1133,7 +1119,6 @@ class ColumnReaderImplBase { void ResetFsstSymbolTable() { fsst_symbol_table_type_ = SymbolTableType::UNDEFINED; fsst_symbol_table_body_.reset(); - dictionary_configured_ = false; } void ConsumeBufferedValues(int64_t num_values) { diff --git a/cpp/src/parquet/column_reader_test.cc b/cpp/src/parquet/column_reader_test.cc index 4c63fe55cdff..404c1027f5bc 100644 --- a/cpp/src/parquet/column_reader_test.cc +++ b/cpp/src/parquet/column_reader_test.cc @@ -426,6 +426,72 @@ TEST_F(TestPrimitiveReader, SkipWholeFsstPage) { ASSERT_FALSE(reader->HasNext()); } +TEST_F(TestPrimitiveReader, DictionaryAndFsstPagesCanCoexist) { + max_def_level_ = 0; + max_rep_level_ = 0; + NodePtr type = schema::ByteArray("a", Repetition::REQUIRED); + const ColumnDescriptor descr(type, max_def_level_, max_rep_level_); + + for (const bool dictionary_first : {true, false}) { + pages_.clear(); + reader_.reset(); + + const std::string dictionary_storage = "dict"; + const std::vector dictionary_values = { + ByteArray(dictionary_storage), + }; + DictionaryPageBuilder dictionary_builder(&descr); + const auto dictionary_indices = dictionary_builder.AppendValues(dictionary_values); + const auto dictionary_page = std::make_shared( + dictionary_builder.WriteDict(), dictionary_builder.num_values(), Encoding::PLAIN); + const auto symbol_table_page = std::make_shared( + Buffer::FromVector(std::vector(9, 0)), SymbolTableType::FSST, + /*is_compressed=*/false); + + if (dictionary_first) { + pages_.push_back(dictionary_page); + pages_.push_back(symbol_table_page); + } else { + pages_.push_back(symbol_table_page); + pages_.push_back(dictionary_page); + } + + pages_.push_back(MakeDataPage( + &descr, /*values=*/{}, /*num_vals=*/1, Encoding::RLE_DICTIONARY, + dictionary_indices->data(), static_cast(dictionary_indices->size()), + /*def_levels=*/{}, max_def_level_, /*rep_levels=*/{}, max_rep_level_)); + + const std::vector fsst_data = { + 0, // PLAIN offsets + 1, 0, 0, 0, // physical value count + 4, 0, 0, 0, // offset section size + 8, 0, 0, 0, // end offset + 0xFF, 'f', 0xFF, 's', 0xFF, 's', 0xFF, 't' // escaped value + }; + pages_.push_back(MakeDataPage( + &descr, /*values=*/{}, /*num_vals=*/1, Encoding::FSST, fsst_data.data(), + static_cast(fsst_data.size()), /*def_levels=*/{}, max_def_level_, + /*rep_levels=*/{}, max_rep_level_)); + + InitReader(&descr); + auto* reader = static_cast(reader_.get()); + std::vector actual(2); + int64_t values_read = 0; + ASSERT_EQ(reader->ReadBatch(2, /*def_levels=*/nullptr, /*rep_levels=*/nullptr, + actual.data(), &values_read), + 1); + ASSERT_EQ(values_read, 1); + EXPECT_EQ(static_cast(actual[0]), "dict"); + + ASSERT_EQ(reader->ReadBatch(1, /*def_levels=*/nullptr, /*rep_levels=*/nullptr, + actual.data() + 1, &values_read), + 1); + ASSERT_EQ(values_read, 1); + EXPECT_EQ(static_cast(actual[1]), "fsst"); + EXPECT_FALSE(reader->HasNext()); + } +} + // GH-41321: When max_def_level > 0 or max_rep_level > 0, and // Page has more or less levels than the `num_values` in // PageHeader. We should detect and throw exception. diff --git a/cpp/src/parquet/column_writer.cc b/cpp/src/parquet/column_writer.cc index 03443cf56c3f..1e27a81ca696 100644 --- a/cpp/src/parquet/column_writer.cc +++ b/cpp/src/parquet/column_writer.cc @@ -265,7 +265,6 @@ class SerializedPageWriter : public PageWriter { pool_(pool), num_values_(0), dictionary_page_offset_(0), - has_dictionary_page_(false), symbol_table_page_offset_(0), symbol_table_page_length_(0), has_symbol_table_page_(false), @@ -289,10 +288,6 @@ class SerializedPageWriter : public PageWriter { } int64_t WriteDictionaryPage(const DictionaryPage& page) override { - if (has_symbol_table_page_) { - throw ParquetException( - "Dictionary and symbol table pages cannot coexist in a column chunk"); - } int64_t uncompressed_size = page.buffer()->size(); if (uncompressed_size > std::numeric_limits::max()) { throw ParquetException( @@ -347,7 +342,6 @@ class SerializedPageWriter : public PageWriter { if (dictionary_page_offset_ == 0) { dictionary_page_offset_ = start_pos; } - has_dictionary_page_ = true; if (meta_encryptor_) { UpdateEncryption(encryption::kDictionaryPageHeader); @@ -364,15 +358,11 @@ class SerializedPageWriter : public PageWriter { } int64_t WriteSymbolTablePage(const SymbolTablePage& page) override { - if (has_dictionary_page_) { - throw ParquetException( - "Dictionary and symbol table pages cannot coexist in a column chunk"); - } if (has_symbol_table_page_) { - throw ParquetException("Column cannot have more than one symbol table page"); + throw ParquetException("Duplicate symbol table page"); } if (page.symbol_table_type() != SymbolTableType::FSST) { - throw ParquetException("Only FSST8 symbol tables are currently supported"); + throw ParquetException("Unsupported symbol table type"); } if (page.buffer() == nullptr) { throw ParquetException("Symbol table page has no body"); @@ -698,7 +688,6 @@ class SerializedPageWriter : public PageWriter { MemoryPool* pool_; int64_t num_values_; int64_t dictionary_page_offset_; - bool has_dictionary_page_; int64_t symbol_table_page_offset_; int32_t symbol_table_page_length_; bool has_symbol_table_page_; @@ -903,6 +892,13 @@ class ColumnWriterImpl { // Finalize auxiliary encoding state after all buffered data pages are written. virtual void FinalizeEncoding() {} + // Called after a data page has been retained while an FSST table is trained. + virtual void OnFsstDataPageDeferred() {} + + // Encode the oldest data page retained by an FSST encoder. Implementations + // update encoding_ to the encoding selected for that page. + virtual std::shared_ptr GetNextBufferedFsstValues() { return nullptr; } + // A convenience struct to combine the encoded statistics and size statistics struct StatisticsPair { EncodedStatistics encoded_stats; @@ -932,6 +928,8 @@ class ColumnWriterImpl { int64_t repetition_levels_rle_size, int64_t uncompressed_size, const std::shared_ptr& values); + void FlushDeferredFsstPages(); + // Serializes Data Pages void WriteDataPage(const DataPage& page) { total_bytes_written_ += pager_->WriteDataPage(page); @@ -1022,6 +1020,20 @@ class ColumnWriterImpl { std::vector> data_pages_; + struct DeferredFsstPage { + std::shared_ptr definition_levels; + int64_t definition_levels_size; + std::shared_ptr repetition_levels; + int64_t repetition_levels_size; + int32_t num_values; + int32_t null_count; + int32_t num_rows; + int64_t first_row_index; + EncodedStatistics statistics; + SizeStatistics size_statistics; + }; + std::vector deferred_fsst_pages_; + std::optional content_defined_chunker_; private: @@ -1093,6 +1105,38 @@ void ColumnWriterImpl::AddDataPage() { descr_->max_repetition_level(), /*include_length_prefix=*/is_v1_data_page); } + if (values == nullptr) { + auto [page_stats, page_size_stats] = GetPageStatistics(); + page_stats.ApplyStatSizeLimits(properties_->max_statistics_size(descr_->path())); + page_stats.set_is_signed(SortOrder::SIGNED == descr_->sort_order()); + ResetPageStatistics(); + + DeferredFsstPage deferred; + PARQUET_ASSIGN_OR_THROW( + deferred.definition_levels, + definition_levels_rle_->CopySlice(0, definition_levels_rle_size, allocator_)); + deferred.definition_levels_size = definition_levels_rle_size; + PARQUET_ASSIGN_OR_THROW( + deferred.repetition_levels, + repetition_levels_rle_->CopySlice(0, repetition_levels_rle_size, allocator_)); + deferred.repetition_levels_size = repetition_levels_rle_size; + deferred.num_values = static_cast(num_buffered_values_); + deferred.null_count = static_cast(num_buffered_nulls_); + deferred.num_rows = static_cast(num_buffered_rows_); + deferred.first_row_index = rows_written_ - num_buffered_rows_; + deferred.statistics = std::move(page_stats); + deferred.size_statistics = std::move(page_size_stats); + deferred_fsst_pages_.push_back(std::move(deferred)); + + InitSinks(); + num_buffered_values_ = 0; + num_buffered_encoded_values_ = 0; + num_buffered_rows_ = 0; + num_buffered_nulls_ = 0; + OnFsstDataPageDeferred(); + return; + } + int64_t uncompressed_size = definition_levels_rle_size + repetition_levels_rle_size + values->size(); @@ -1112,6 +1156,87 @@ void ColumnWriterImpl::AddDataPage() { num_buffered_nulls_ = 0; } +void ColumnWriterImpl::FlushDeferredFsstPages() { + const bool is_v1_data_page = + properties_->data_page_version() == ParquetDataPageVersion::V1; + for (auto& deferred : deferred_fsst_pages_) { + auto values = GetNextBufferedFsstValues(); + if (values == nullptr) { + throw ParquetException("FSST encoder did not provide a buffered data page"); + } + + if (is_v1_data_page) { + const int64_t uncompressed_size = deferred.definition_levels_size + + deferred.repetition_levels_size + values->size(); + PARQUET_THROW_NOT_OK(uncompressed_data_->Resize(uncompressed_size, false)); + uint8_t* output = uncompressed_data_->mutable_data(); + if (deferred.repetition_levels_size > 0) { + std::memcpy(output, deferred.repetition_levels->data(), + static_cast(deferred.repetition_levels_size)); + } + output += deferred.repetition_levels_size; + if (deferred.definition_levels_size > 0) { + std::memcpy(output, deferred.definition_levels->data(), + static_cast(deferred.definition_levels_size)); + } + output += deferred.definition_levels_size; + if (values->size() > 0) { + std::memcpy(output, values->data(), static_cast(values->size())); + } + + std::shared_ptr compressed_data; + if (pager_->has_compressor()) { + pager_->Compress(*uncompressed_data_, compressor_temp_buffer_.get()); + compressed_data = compressor_temp_buffer_; + } else { + compressed_data = uncompressed_data_; + } + DataPageV1 page(compressed_data, deferred.num_values, encoding_, Encoding::RLE, + Encoding::RLE, uncompressed_size, std::move(deferred.statistics), + deferred.first_row_index, std::move(deferred.size_statistics)); + WriteDataPage(page); + } else { + bool page_is_compressed = false; + if (pager_->has_compressor() && values->size() > 0) { + pager_->Compress(*values, compressor_temp_buffer_.get()); + page_is_compressed = compressor_temp_buffer_->size() < values->size(); + } + const std::shared_ptr compressed_values = + page_is_compressed ? compressor_temp_buffer_ : values; + const int64_t combined_size = deferred.definition_levels_size + + deferred.repetition_levels_size + + compressed_values->size(); + auto combined = AllocateBuffer(allocator_, combined_size); + uint8_t* output = combined->mutable_data(); + if (deferred.repetition_levels_size > 0) { + std::memcpy(output, deferred.repetition_levels->data(), + static_cast(deferred.repetition_levels_size)); + } + output += deferred.repetition_levels_size; + if (deferred.definition_levels_size > 0) { + std::memcpy(output, deferred.definition_levels->data(), + static_cast(deferred.definition_levels_size)); + } + output += deferred.definition_levels_size; + if (compressed_values->size() > 0) { + std::memcpy(output, compressed_values->data(), + static_cast(compressed_values->size())); + } + + DataPageV2 page(combined, deferred.num_values, deferred.null_count, + deferred.num_rows, encoding_, + static_cast(deferred.definition_levels_size), + static_cast(deferred.repetition_levels_size), + deferred.definition_levels_size + deferred.repetition_levels_size + + values->size(), + page_is_compressed, std::move(deferred.statistics), + deferred.first_row_index, std::move(deferred.size_statistics)); + WriteDataPage(page); + } + } + deferred_fsst_pages_.clear(); +} + void ColumnWriterImpl::BuildDataPageV1(int64_t definition_levels_rle_size, int64_t repetition_levels_rle_size, int64_t uncompressed_size, @@ -1403,7 +1528,8 @@ class TypedColumnWriterImpl : public ColumnWriterImpl, if (!use_dictionary && encoding == Encoding::FSST) { current_encoder_ = MakeFsstEncoder(descr_, properties->fsst_offset_encoding(descr_->path()), - properties->memory_pool()); + properties->memory_pool(), + properties->fsst_training_data_pages(descr_->path())); } else { current_encoder_ = MakeEncoder(ParquetType::type_num, encoding, use_dictionary, descr_, properties->memory_pool()); @@ -1626,27 +1752,39 @@ class TypedColumnWriterImpl : public ColumnWriterImpl, std::shared_ptr GetValuesBuffer() override { auto values = current_encoder_->FlushValues(); if (current_encoder_->encoding() == Encoding::FSST) { - encoding_ = current_encoder_->page_encoding(); - if (!fsst_symbol_table_written_) { - auto symbol_table = current_encoder_->fsst_symbol_table(); - if (symbol_table == nullptr) { - throw ParquetException("FSST encoder did not provide a symbol table"); - } - SymbolTablePage page(symbol_table, current_encoder_->fsst_symbol_table_type(), - /*is_compressed=*/false); - total_bytes_written_ += pager_->WriteSymbolTablePage(page); - fsst_symbol_table_written_ = true; + if (values != nullptr) { + encoding_ = current_encoder_->page_encoding(); } + WriteFsstSymbolTableIfReady(); } return values; } + void OnFsstDataPageDeferred() override { + if (current_encoder_->fsst_training_complete()) { + WriteFsstSymbolTableIfReady(); + FlushDeferredFsstPages(); + } + } + + std::shared_ptr GetNextBufferedFsstValues() override { + auto values = current_encoder_->FlushBufferedFsstPage(); + encoding_ = current_encoder_->page_encoding(); + return values; + } + void FinalizeEncoding() override { - if (current_encoder_->encoding() == Encoding::FSST && !fsst_symbol_table_written_) { - auto values = GetValuesBuffer(); - if (values->size() != 0) { - throw ParquetException("FSST encoder retained values after the final data page"); - } + if (current_encoder_->encoding() != Encoding::FSST) { + return; + } + current_encoder_->FinishFsstTraining(); + WriteFsstSymbolTableIfReady(); + if (current_encoder_->num_buffered_fsst_pages() != deferred_fsst_pages_.size()) { + throw ParquetException("FSST buffered page state is inconsistent"); + } + FlushDeferredFsstPages(); + if (current_encoder_->num_buffered_fsst_pages() != 0) { + throw ParquetException("FSST encoder retained data pages after finalization"); } } @@ -1789,6 +1927,20 @@ class TypedColumnWriterImpl : public ColumnWriterImpl, using BloomFilterWriter = TypedBloomFilterWriter; std::unique_ptr current_encoder_; bool fsst_symbol_table_written_ = false; + + void WriteFsstSymbolTableIfReady() { + if (fsst_symbol_table_written_ || !current_encoder_->fsst_training_complete()) { + return; + } + auto symbol_table = current_encoder_->fsst_symbol_table(); + if (symbol_table == nullptr) { + throw ParquetException("FSST encoder did not provide a symbol table"); + } + SymbolTablePage page(symbol_table, current_encoder_->fsst_symbol_table_type(), + /*is_compressed=*/false); + total_bytes_written_ += pager_->WriteSymbolTablePage(page); + fsst_symbol_table_written_ = true; + } // Downcasted observers of current_encoder_. // The downcast is performed once as opposed to at every use since // dynamic_cast is so expensive, and static_cast is not available due diff --git a/cpp/src/parquet/decoder.cc b/cpp/src/parquet/decoder.cc index b3ea37de7705..8f342b8dc605 100644 --- a/cpp/src/parquet/decoder.cc +++ b/cpp/src/parquet/decoder.cc @@ -2555,7 +2555,7 @@ std::unique_ptr MakeFsstDecoder(const ColumnDescriptor* descr, throw ParquetException("FSST decoder only supports BYTE_ARRAY"); } if (symbol_table_type != SymbolTableType::FSST) { - throw ParquetException("Only FSST8 symbol tables are currently supported"); + throw ParquetException("FSST decoder requires an FSST symbol table"); } return std::make_unique(descr, symbol_table_body, pool); } diff --git a/cpp/src/parquet/encoder.cc b/cpp/src/parquet/encoder.cc index 0da4f60496df..59e2cec48871 100644 --- a/cpp/src/parquet/encoder.cc +++ b/cpp/src/parquet/encoder.cc @@ -1301,12 +1301,19 @@ void DeltaBitPackEncoder::PutSpaced(const T* src, int num_values, class FsstEncoder final : public EncoderImpl, virtual public TypedEncoder { public: FsstEncoder(const ColumnDescriptor* descr, FsstOffsetEncoding::type offset_encoding, - MemoryPool* pool) - : EncoderImpl(descr, Encoding::FSST, pool), offset_encoding_(offset_encoding) { + MemoryPool* pool, int32_t training_data_pages) + : EncoderImpl(descr, Encoding::FSST, pool), + offset_encoding_(offset_encoding), + training_data_pages_(training_data_pages) { if (offset_encoding != FsstOffsetEncoding::PLAIN && offset_encoding != FsstOffsetEncoding::DELTA_BINARY_PACKED) { throw ParquetException("Unsupported FSST offset encoding"); } + if (training_data_pages == 0 || training_data_pages < -1) { + throw ParquetException( + "FSST training data pages must be positive, or -1 for the entire column " + "chunk"); + } } using TypedEncoder::Put; @@ -1327,6 +1334,16 @@ class FsstEncoder final : public EncoderImpl, virtual public TypedEncoder FlushBufferedFsstPage() override; + void Put(const ByteArray* src, int num_values) override { if (num_values < 0 || static_cast(values_.size()) + static_cast(num_values) > @@ -1396,21 +1413,29 @@ class FsstEncoder final : public EncoderImpl, virtual public TypedEncoder EncodePlain() const; + std::shared_ptr EncodeValues(std::vector values, + int64_t values_byte_size); + std::shared_ptr EncodePlain(const std::vector& values, + int64_t values_byte_size) const; const FsstOffsetEncoding::type offset_encoding_; + const int32_t training_data_pages_; Encoding::type page_encoding_ = Encoding::FSST; std::shared_ptr symbol_table_; std::shared_ptr symbol_table_body_; std::vector values_; int64_t values_byte_size_ = 0; + std::vector> buffered_page_values_; + std::vector buffered_page_byte_sizes_; + size_t buffered_page_index_ = 0; }; -std::shared_ptr FsstEncoder::EncodePlain() const { +std::shared_ptr FsstEncoder::EncodePlain(const std::vector& values, + int64_t values_byte_size) const { ::arrow::BufferBuilder sink(pool_); PARQUET_THROW_NOT_OK( - sink.Reserve(values_byte_size_ + static_cast(values_.size()) * 4)); - for (const std::string& value : values_) { + sink.Reserve(values_byte_size + static_cast(values.size()) * 4)); + for (const std::string& value : values) { const uint32_t length = ::arrow::bit_util::ToLittleEndian(static_cast(value.size())); PARQUET_THROW_NOT_OK( @@ -1423,25 +1448,74 @@ std::shared_ptr FsstEncoder::EncodePlain() const { } std::shared_ptr FsstEncoder::FlushValues() { - if (symbol_table_ == nullptr) { + if (symbol_table_ != nullptr) { + auto values = std::move(values_); + const int64_t values_byte_size = values_byte_size_; + values_.clear(); + values_byte_size_ = 0; + return EncodeValues(std::move(values), values_byte_size); + } + + if (training_data_pages_ == 1) { symbol_table_ = internal::FsstSymbolTable::Train(values_); symbol_table_body_ = symbol_table_->Serialize(pool_); + auto values = std::move(values_); + const int64_t values_byte_size = values_byte_size_; + values_.clear(); + values_byte_size_ = 0; + return EncodeValues(std::move(values), values_byte_size); } + buffered_page_values_.push_back(std::move(values_)); + buffered_page_byte_sizes_.push_back(values_byte_size_); + values_.clear(); + values_byte_size_ = 0; + if (training_data_pages_ != -1 && + buffered_page_values_.size() >= static_cast(training_data_pages_)) { + FinishFsstTraining(); + } + return nullptr; +} + +void FsstEncoder::FinishFsstTraining() { + if (symbol_table_ != nullptr) { + return; + } + symbol_table_ = internal::FsstSymbolTable::TrainBatches(buffered_page_values_); + symbol_table_body_ = symbol_table_->Serialize(pool_); +} + +std::shared_ptr FsstEncoder::FlushBufferedFsstPage() { + if (symbol_table_ == nullptr) { + throw ParquetException("FSST symbol table has not been trained"); + } + if (buffered_page_index_ >= buffered_page_values_.size()) { + throw ParquetException("FSST encoder has no buffered data page"); + } + auto output = EncodeValues(std::move(buffered_page_values_[buffered_page_index_]), + buffered_page_byte_sizes_[buffered_page_index_]); + ++buffered_page_index_; + if (buffered_page_index_ == buffered_page_values_.size()) { + buffered_page_values_.clear(); + buffered_page_byte_sizes_.clear(); + buffered_page_index_ = 0; + } + return output; +} + +std::shared_ptr FsstEncoder::EncodeValues(std::vector values, + int64_t values_byte_size) { std::vector end_offsets; - end_offsets.reserve(values_.size()); + end_offsets.reserve(values.size()); const int64_t plain_size = - values_byte_size_ + static_cast(values_.size()) * sizeof(uint32_t); + values_byte_size + static_cast(values.size()) * sizeof(uint32_t); const size_t maximum_compressed_data_size = static_cast(plain_size > 9 ? plain_size - 9 : 0); std::vector compressed_data; - if (!symbol_table_->CompressBatch(values_, maximum_compressed_data_size, &end_offsets, + if (!symbol_table_->CompressBatch(values, maximum_compressed_data_size, &end_offsets, &compressed_data)) { page_encoding_ = Encoding::PLAIN; - auto output = EncodePlain(); - values_.clear(); - values_byte_size_ = 0; - return output; + return EncodePlain(values, values_byte_size); } std::shared_ptr encoded_offsets; @@ -1468,7 +1542,7 @@ std::shared_ptr FsstEncoder::FlushValues() { static_cast(compressed_data.size()))); const uint8_t offset_encoding = static_cast(offset_encoding_); const int32_t num_values = - ::arrow::bit_util::ToLittleEndian(static_cast(values_.size())); + ::arrow::bit_util::ToLittleEndian(static_cast(values.size())); const int32_t offset_size = ::arrow::bit_util::ToLittleEndian(static_cast(encoded_offsets->size())); PARQUET_THROW_NOT_OK(sink.Append(&offset_encoding, 1)); @@ -1484,13 +1558,11 @@ std::shared_ptr FsstEncoder::FlushValues() { std::shared_ptr output; if (fsst_output->size() >= plain_size) { page_encoding_ = Encoding::PLAIN; - output = EncodePlain(); + output = EncodePlain(values, values_byte_size); } else { page_encoding_ = Encoding::FSST; output = std::move(fsst_output); } - values_.clear(); - values_byte_size_ = 0; return output; } @@ -1968,11 +2040,11 @@ std::shared_ptr RleBooleanEncoder::FlushValues() { std::unique_ptr MakeFsstEncoder(const ColumnDescriptor* descr, FsstOffsetEncoding::type offset_encoding, - MemoryPool* pool) { + MemoryPool* pool, int32_t training_data_pages) { if (descr == nullptr || descr->physical_type() != Type::BYTE_ARRAY) { throw ParquetException("FSST encoder only supports BYTE_ARRAY"); } - return std::make_unique(descr, offset_encoding, pool); + return std::make_unique(descr, offset_encoding, pool, training_data_pages); } std::unique_ptr MakeEncoder(Type::type type_num, Encoding::type encoding, diff --git a/cpp/src/parquet/encoding.h b/cpp/src/parquet/encoding.h index a9dc3f5f28f1..8f4ea03a5070 100644 --- a/cpp/src/parquet/encoding.h +++ b/cpp/src/parquet/encoding.h @@ -174,6 +174,20 @@ class Encoder { return SymbolTableType::UNDEFINED; } + /// Whether an FSST encoder has collected its configured training pages and + /// produced the shared symbol table. + virtual bool fsst_training_complete() const { return true; } + + /// Complete FSST training with the pages collected so far. Used when a + /// column chunk ends before its configured training-page count is reached. + virtual void FinishFsstTraining() {} + + /// Number of data pages retained until the shared FSST table is trained. + virtual size_t num_buffered_fsst_pages() const { return 0; } + + /// Encode and remove the oldest retained FSST data page. + virtual std::shared_ptr FlushBufferedFsstPage() { return nullptr; } + virtual void Put(const ::arrow::Array& values) = 0; // Report the number of bytes written to the encoder since the last report. @@ -439,7 +453,8 @@ std::unique_ptr MakeEncoder( PARQUET_EXPORT std::unique_ptr MakeFsstEncoder( const ColumnDescriptor* descr, FsstOffsetEncoding::type offset_encoding, - ::arrow::MemoryPool* pool = ::arrow::default_memory_pool()); + ::arrow::MemoryPool* pool = ::arrow::default_memory_pool(), + int32_t training_data_pages = 1); template std::unique_ptr::Encoder> MakeTypedEncoder( diff --git a/cpp/src/parquet/encoding_test.cc b/cpp/src/parquet/encoding_test.cc index 352acc0951d2..805b01feec97 100644 --- a/cpp/src/parquet/encoding_test.cc +++ b/cpp/src/parquet/encoding_test.cc @@ -2709,6 +2709,65 @@ INSTANTIATE_TEST_SUITE_P(Fsst8WithBothOffsetEncodings, FsstEncodingTest, ::testing::Values(FsstOffsetEncoding::PLAIN, FsstOffsetEncoding::DELTA_BINARY_PACKED)); +TEST(FsstEncoding, ConfigurableTrainingPageCount) { + auto node = schema::ByteArray("value", Repetition::REQUIRED); + ColumnDescriptor descr(node, 0, 0); + + for (const int32_t training_pages : {2, 10, -1}) { + auto base_encoder = MakeFsstEncoder(&descr, FsstOffsetEncoding::PLAIN, + ::arrow::default_memory_pool(), training_pages); + auto* encoder = dynamic_cast(base_encoder.get()); + ASSERT_NE(encoder, nullptr); + + std::vector first_page; + std::vector second_page; + for (int i = 0; i < 100; ++i) { + first_page.push_back("https://arrow.apache.org/fsst/first/" + + std::to_string(i % 5)); + second_page.push_back("https://arrow.apache.org/fsst/second/" + + std::to_string(i % 7)); + } + auto put_page = [&](const std::vector& page) { + std::vector values; + values.reserve(page.size()); + for (const auto& value : page) { + values.emplace_back(value); + } + encoder->Put(values.data(), static_cast(values.size())); + ASSERT_EQ(encoder->FlushValues(), nullptr); + }; + + put_page(first_page); + ASSERT_FALSE(base_encoder->fsst_training_complete()); + ASSERT_EQ(base_encoder->num_buffered_fsst_pages(), 1); + put_page(second_page); + ASSERT_EQ(base_encoder->num_buffered_fsst_pages(), 2); + ASSERT_EQ(base_encoder->fsst_training_complete(), training_pages == 2); + + base_encoder->FinishFsstTraining(); + ASSERT_TRUE(base_encoder->fsst_training_complete()); + ASSERT_NE(base_encoder->fsst_symbol_table(), nullptr); + + for (const auto* expected : {&first_page, &second_page}) { + auto encoded = base_encoder->FlushBufferedFsstPage(); + ASSERT_EQ(base_encoder->page_encoding(), Encoding::FSST); + auto decoder = MakeFsstDecoder(&descr, SymbolTableType::FSST, + base_encoder->fsst_symbol_table()); + decoder->SetData(static_cast(expected->size()), encoded->data(), + static_cast(encoded->size())); + auto* typed_decoder = dynamic_cast(decoder.get()); + ASSERT_NE(typed_decoder, nullptr); + std::vector actual(expected->size()); + ASSERT_EQ(typed_decoder->Decode(actual.data(), static_cast(actual.size())), + actual.size()); + for (size_t i = 0; i < actual.size(); ++i) { + ASSERT_EQ(static_cast(actual[i]), (*expected)[i]); + } + } + ASSERT_EQ(base_encoder->num_buffered_fsst_pages(), 0); + } +} + TEST(FsstEncoding, FallsBackToPlainWhenEncodingExpandsPage) { auto node = schema::ByteArray("value", Repetition::REQUIRED); ColumnDescriptor descr(node, 0, 0); diff --git a/cpp/src/parquet/file_serialize_test.cc b/cpp/src/parquet/file_serialize_test.cc index 15dc4c1265a3..f2605ed2169a 100644 --- a/cpp/src/parquet/file_serialize_test.cc +++ b/cpp/src/parquet/file_serialize_test.cc @@ -546,6 +546,76 @@ TEST(TestBufferedRowGroupWriter, EmptyFsstColumnWritesSymbolTablePage) { ASSERT_EQ(row_group->file_offset(), column_metadata->symbol_table_page_offset()); } +TEST(ParquetRoundtrip, FsstTrainingPageFlushPolicy) { + for (const int32_t training_pages : {2, -1}) { + auto sink = CreateOutputStream(); + auto writer_props = parquet::WriterProperties::Builder() + .disable_dictionary() + ->encoding(Encoding::FSST) + ->fsst_training_data_pages(training_pages) + ->data_pagesize(64) + ->build(); + schema::NodeVector fields; + fields.push_back(PrimitiveNode::Make("col", Repetition::REQUIRED, Type::BYTE_ARRAY)); + auto schema = std::static_pointer_cast( + GroupNode::Make("schema", Repetition::REQUIRED, fields)); + auto file_writer = ParquetFileWriter::Open(sink, schema, writer_props); + auto row_group_writer = file_writer->AppendRowGroup(); + auto column_writer = static_cast(row_group_writer->NextColumn()); + + std::vector strings; + std::vector values; + for (int i = 0; i < 300; ++i) { + strings.push_back("https://arrow.apache.org/parquet/fsst/training/" + + std::to_string(i % 17)); + } + values.reserve(strings.size()); + for (const auto& value : strings) { + values.emplace_back(value); + } + + column_writer->WriteBatch(100, nullptr, nullptr, values.data()); + ASSERT_EQ(column_writer->total_compressed_bytes_written(), 0); + column_writer->WriteBatch(100, nullptr, nullptr, values.data() + 100); + const int64_t bytes_after_second_page = + column_writer->total_compressed_bytes_written(); + if (training_pages == -1) { + ASSERT_EQ(bytes_after_second_page, 0); + } else { + ASSERT_GT(bytes_after_second_page, 0); + } + column_writer->WriteBatch(100, nullptr, nullptr, values.data() + 200); + if (training_pages == -1) { + ASSERT_EQ(column_writer->total_compressed_bytes_written(), 0); + } else { + ASSERT_GT(column_writer->total_compressed_bytes_written(), bytes_after_second_page); + } + + column_writer->Close(); + row_group_writer->Close(); + file_writer->Close(); + PARQUET_ASSIGN_OR_THROW(auto buffer, sink->Finish()); + + auto file_reader = ParquetFileReader::Open( + std::make_shared<::arrow::io::BufferReader>(std::move(buffer))); + auto reader = + std::static_pointer_cast(file_reader->RowGroup(0)->Column(0)); + std::vector actual(values.size()); + int64_t total_values_read = 0; + while (reader->HasNext()) { + int64_t values_read = 0; + reader->ReadBatch(actual.size() - total_values_read, nullptr, nullptr, + actual.data() + total_values_read, &values_read); + for (int64_t i = 0; i < values_read; ++i) { + ASSERT_EQ(static_cast(actual[total_values_read + i]), + static_cast(values[total_values_read + i])); + } + total_values_read += values_read; + } + ASSERT_EQ(total_values_read, actual.size()); + } +} + TEST(ParquetRoundtrip, AllNulls) { auto primitive_node = PrimitiveNode::Make("nulls", Repetition::OPTIONAL, nullptr, Type::INT32); diff --git a/cpp/src/parquet/fsst_internal.cc b/cpp/src/parquet/fsst_internal.cc index 0222032fef8f..044257ca2627 100644 --- a/cpp/src/parquet/fsst_internal.cc +++ b/cpp/src/parquet/fsst_internal.cc @@ -63,19 +63,45 @@ std::shared_ptr FsstSymbolTable::Train( const std::vector& values) { std::vector lengths; std::vector inputs; - if (values.empty()) { + lengths.reserve(values.size()); + inputs.reserve(values.size()); + for (const std::string& value : values) { + lengths.push_back(value.size()); + inputs.push_back(reinterpret_cast(value.data())); + } + return TrainFromInputs(std::move(lengths), std::move(inputs)); +} + +std::shared_ptr FsstSymbolTable::TrainBatches( + const std::vector>& value_batches) { + size_t num_values = 0; + for (const auto& batch : value_batches) { + if (batch.size() > std::numeric_limits::max() - num_values) { + throw ParquetException("FSST training corpus contains too many values"); + } + num_values += batch.size(); + } + std::vector lengths; + std::vector inputs; + lengths.reserve(num_values); + inputs.reserve(num_values); + for (const auto& batch : value_batches) { + for (const std::string& value : batch) { + lengths.push_back(value.size()); + inputs.push_back(reinterpret_cast(value.data())); + } + } + return TrainFromInputs(std::move(lengths), std::move(inputs)); +} + +std::shared_ptr FsstSymbolTable::TrainFromInputs( + std::vector lengths, std::vector inputs) { + if (inputs.empty()) { // CWI expects at least one input slot. A zero-length sample produces a // fully usable table for encoding values on later pages. static constexpr unsigned char kEmptyInput = 0; lengths.push_back(0); inputs.push_back(&kEmptyInput); - } else { - lengths.reserve(values.size()); - inputs.reserve(values.size()); - for (const std::string& value : values) { - lengths.push_back(value.size()); - inputs.push_back(reinterpret_cast(value.data())); - } } auto trained = std::make_unique(); diff --git a/cpp/src/parquet/fsst_internal.h b/cpp/src/parquet/fsst_internal.h index 0b96549db43e..b6b6d3cc476c 100644 --- a/cpp/src/parquet/fsst_internal.h +++ b/cpp/src/parquet/fsst_internal.h @@ -40,6 +40,9 @@ class PARQUET_EXPORT FsstSymbolTable { static std::shared_ptr Train(const std::vector& values); + static std::shared_ptr TrainBatches( + const std::vector>& value_batches); + static std::shared_ptr Deserialize( const std::shared_ptr<::arrow::Buffer>& body); @@ -60,6 +63,8 @@ class PARQUET_EXPORT FsstSymbolTable { struct CwiState; explicit FsstSymbolTable(std::vector symbols); + static std::shared_ptr TrainFromInputs( + std::vector lengths, std::vector inputs); void InitializeCwiDecoder(); std::vector symbols_; diff --git a/cpp/src/parquet/properties.cc b/cpp/src/parquet/properties.cc index 858258c670b8..cef3a8329fa0 100644 --- a/cpp/src/parquet/properties.cc +++ b/cpp/src/parquet/properties.cc @@ -108,6 +108,11 @@ void WriterProperties::Builder::CopyColumnSpecificProperties( this->fsst_offset_encoding(col_path, col_props.fsst_offset_encoding()); } + if (col_props.fsst_training_data_pages() != + default_column_properties_.fsst_training_data_pages()) { + this->fsst_training_data_pages(col_path, col_props.fsst_training_data_pages()); + } + if (col_props.bloom_filter_options().has_value()) { this->enable_bloom_filter(col_path, col_props.bloom_filter_options().value()); } diff --git a/cpp/src/parquet/properties.h b/cpp/src/parquet/properties.h index e66b5b97fb3b..a3992187d19d 100644 --- a/cpp/src/parquet/properties.h +++ b/cpp/src/parquet/properties.h @@ -168,6 +168,7 @@ static constexpr Encoding::type DEFAULT_ENCODING = Encoding::UNKNOWN; static const char DEFAULT_CREATED_BY[] = CREATED_BY_VERSION; static constexpr Compression::type DEFAULT_COMPRESSION_TYPE = Compression::UNCOMPRESSED; static constexpr bool DEFAULT_IS_PAGE_INDEX_ENABLED = true; +static constexpr int32_t DEFAULT_FSST_TRAINING_DATA_PAGES = 1; static constexpr SizeStatisticsLevel DEFAULT_SIZE_STATISTICS_LEVEL = SizeStatisticsLevel::PageAndColumnChunk; @@ -248,6 +249,15 @@ class PARQUET_EXPORT ColumnProperties { fsst_offset_encoding_ = encoding; } + void set_fsst_training_data_pages(int32_t num_pages) { + if (num_pages == 0 || num_pages < -1) { + throw ParquetException( + "FSST training data pages must be positive, or -1 for the entire column " + "chunk"); + } + fsst_training_data_pages_ = num_pages; + } + void set_compression(Compression::type codec) { codec_ = codec; } void set_dictionary_enabled(bool dictionary_enabled) { @@ -294,6 +304,8 @@ class PARQUET_EXPORT ColumnProperties { FsstOffsetEncoding::type fsst_offset_encoding() const { return fsst_offset_encoding_; } + int32_t fsst_training_data_pages() const { return fsst_training_data_pages_; } + Compression::type compression() const { return codec_; } bool dictionary_enabled() const { return dictionary_enabled_; } @@ -322,6 +334,7 @@ class PARQUET_EXPORT ColumnProperties { private: Encoding::type encoding_; FsstOffsetEncoding::type fsst_offset_encoding_ = FsstOffsetEncoding::PLAIN; + int32_t fsst_training_data_pages_ = DEFAULT_FSST_TRAINING_DATA_PAGES; Compression::type codec_; bool dictionary_enabled_; bool statistics_enabled_; @@ -604,6 +617,29 @@ class PARQUET_EXPORT WriterProperties { return fsst_offset_encoding(path->ToDotString(), encoding); } + /// Select how many initial data pages are used to train the shared FSST table. + /// Positive values defer that many pages before training. -1 defers the entire + /// column chunk and trains when the column writer closes. Default is 1. + Builder* fsst_training_data_pages(int32_t num_pages) { + default_column_properties_.set_fsst_training_data_pages(num_pages); + return this; + } + + Builder* fsst_training_data_pages(const std::string& path, int32_t num_pages) { + if (num_pages == 0 || num_pages < -1) { + throw ParquetException( + "FSST training data pages must be positive, or -1 for the entire column " + "chunk"); + } + fsst_training_data_pages_[path] = num_pages; + return this; + } + + Builder* fsst_training_data_pages(const std::shared_ptr& path, + int32_t num_pages) { + return fsst_training_data_pages(path->ToDotString(), num_pages); + } + /// Specify compression codec in general for all columns. /// Default UNCOMPRESSED. Builder* compression(Compression::type codec) { @@ -911,6 +947,8 @@ class PARQUET_EXPORT WriterProperties { for (const auto& item : encodings_) get(item.first).set_encoding(item.second); for (const auto& item : fsst_offset_encodings_) get(item.first).set_fsst_offset_encoding(item.second); + for (const auto& item : fsst_training_data_pages_) + get(item.first).set_fsst_training_data_pages(item.second); for (const auto& item : codecs_) get(item.first).set_compression(item.second); for (const auto& item : codec_options_) get(item.first).set_codec_options(item.second); @@ -965,6 +1003,7 @@ class PARQUET_EXPORT WriterProperties { ColumnProperties default_column_properties_; std::unordered_map encodings_; std::unordered_map fsst_offset_encodings_; + std::unordered_map fsst_training_data_pages_; std::unordered_map codecs_; std::unordered_map> codec_options_; std::unordered_map dictionary_enabled_; @@ -1043,6 +1082,11 @@ class PARQUET_EXPORT WriterProperties { return column_properties(path).fsst_offset_encoding(); } + int32_t fsst_training_data_pages( + const std::shared_ptr& path) const { + return column_properties(path).fsst_training_data_pages(); + } + Compression::type compression(const std::shared_ptr& path) const { return column_properties(path).compression(); } diff --git a/cpp/src/parquet/properties_test.cc b/cpp/src/parquet/properties_test.cc index 58da9c5ea8b4..a5337f72ae75 100644 --- a/cpp/src/parquet/properties_test.cc +++ b/cpp/src/parquet/properties_test.cc @@ -54,6 +54,8 @@ TEST(TestWriterProperties, Basics) { ASSERT_FALSE(props->page_checksum_enabled()); ASSERT_EQ(FsstOffsetEncoding::PLAIN, props->fsst_offset_encoding(ColumnPath::FromDotString("any"))); + ASSERT_EQ(DEFAULT_FSST_TRAINING_DATA_PAGES, + props->fsst_training_data_pages(ColumnPath::FromDotString("any"))); } TEST(TestWriterProperties, DefaultCompression) { @@ -65,6 +67,15 @@ TEST(TestWriterProperties, DefaultCompression) { ::arrow::util::kUseDefaultCompressionLevel); } +TEST(TestWriterProperties, FsstTrainingDataPagesValidation) { + WriterProperties::Builder builder; + ASSERT_THROW(builder.fsst_training_data_pages(0), ParquetException); + ASSERT_THROW(builder.fsst_training_data_pages(-2), ParquetException); + ASSERT_NO_THROW(builder.fsst_training_data_pages(1)); + ASSERT_NO_THROW(builder.fsst_training_data_pages(10)); + ASSERT_NO_THROW(builder.fsst_training_data_pages(-1)); +} + TEST(TestWriterProperties, AdvancedHandling) { WriterProperties::Builder builder; builder.compression("gzip", Compression::GZIP); @@ -318,6 +329,8 @@ TEST_P(WriterPropertiesTest, RoundTripThroughBuilder) { ASSERT_EQ(round_tripped_col.encoding(), column_properties.encoding()); ASSERT_EQ(round_tripped_col.fsst_offset_encoding(), column_properties.fsst_offset_encoding()); + ASSERT_EQ(round_tripped_col.fsst_training_data_pages(), + column_properties.fsst_training_data_pages()); ASSERT_EQ(round_tripped_col.max_statistics_size(), column_properties.max_statistics_size()); ASSERT_EQ(round_tripped_col.page_index_enabled(), @@ -399,6 +412,8 @@ std::vector writer_properties_test_cases() { WriterProperties::Builder builder; builder.fsst_offset_encoding(FsstOffsetEncoding::DELTA_BINARY_PACKED); builder.fsst_offset_encoding(column_a, FsstOffsetEncoding::PLAIN); + builder.fsst_training_data_pages(10); + builder.fsst_training_data_pages(column_a, -1); test_cases.emplace_back(builder.build(), "fsst_column_override"); } { diff --git a/docs/source/cpp/parquet.rst b/docs/source/cpp/parquet.rst index 16c5f7ae7fc6..7b390228076b 100644 --- a/docs/source/cpp/parquet.rst +++ b/docs/source/cpp/parquet.rst @@ -419,7 +419,9 @@ Encodings * \(3) Supported for BYTE_ARRAY columns. Disable dictionary encoding and select ``Encoding::FSST``. Writers use the vendored CWI FSST8 codec and can choose PLAIN or DELTA_BINARY_PACKED end offsets with ``fsst_offset_encoding()``. - The initial implementation supports FSST8 symbol tables. + Supports FSST symbol tables. ``fsst_training_data_pages()`` configures the + number of data pages used to train the symbol table; -1 uses the complete + column chunk. Types -----