25#pragma warning(disable: 4100)
27#pragma warning(disable: 4266)
29#pragma warning(disable: 4324)
31#pragma warning(disable: 4355)
33#pragma warning(disable: 4435)
35#pragma warning(disable: 4458)
37#pragma warning(disable: 4800)
40#include <arrow/io/api.h>
41#include <parquet/arrow/writer.h>
57#pragma warning(disable: 4266)
64 arrow::Status
Close()
override {
66 return arrow::Status::OK();
69 arrow::Status
Flush()
override {
71 return arrow::Status::OK();
74 arrow::Result<int64_t>
Tell()
const override {
82 arrow::Status
Write(
const void* data, int64_t nbytes)
override {
84 return arrow::Status::IOError(
"Write on closed stream");
86 myOStream.write(
reinterpret_cast<const char*
>(data), nbytes);
88 return arrow::Status::IOError(
"Failed to write to ostream");
90 return arrow::Status::OK();
106 Impl(
const std::string& columnNames,
const int batchSize)
116 parquet::Compression::type
myCompression = parquet::Compression::UNCOMPRESSED;
122 std::shared_ptr<arrow::Schema>
mySchema = arrow::schema({});
128 std::vector<std::shared_ptr<arrow::ArrayBuilder> >
myBuilders;
134 std::vector<std::shared_ptr<arrow::Scalar> >
myValues;
161 throw ProcessError(
TLF(
"Unexpected attribute '%', this file format does not support Parquet output yet.",
toString(attr)));
166 template <
class ATTR_TYPE,
class BUILDER>
167 void checkBuilder(
const ATTR_TYPE& attr,
const std::shared_ptr<arrow::DataType>& (*dataType)()) {
170 std::string fieldName =
toString(attr);
173 prefix += entry.first +
"_";
175 const std::string fullHeaderName = prefix + fieldName;
179 fieldName =
myXMLStack.back().first +
"_" + fieldName;
184 auto builder = std::make_shared<BUILDER>();
187 PARQUET_THROW_NOT_OK(builder->AppendNulls(
myBuilders.back()->length()));
208 if (compression ==
"snappy") {
209 myImpl->myCompression = parquet::Compression::SNAPPY;
210 }
else if (compression ==
"gzip") {
211 myImpl->myCompression = parquet::Compression::GZIP;
212 }
else if (compression ==
"brotli") {
213 myImpl->myCompression = parquet::Compression::BROTLI;
214 }
else if (compression ==
"zstd") {
215 myImpl->myCompression = parquet::Compression::ZSTD;
216 }
else if (compression ==
"lz4") {
217 myImpl->myCompression = parquet::Compression::LZ4;
218 }
else if (compression ==
"bz2") {
219 myImpl->myCompression = parquet::Compression::BZ2;
220 }
else if (compression !=
"" && compression !=
"uncompressed") {
223 if (!arrow::util::Codec::IsAvailable(
myImpl->myCompression)) {
224 WRITE_WARNINGF(
"Compression '%' not available, falling back to uncompressed.", compression);
225 myImpl->myCompression = parquet::Compression::UNCOMPRESSED;
235 const std::map<SumoXMLAttr, std::string>& attrs,
bool ,
237 if (attrs.size() > 2) {
238 myImpl->myHaveRootAttrs =
true;
240 for (
const auto& a : attrs) {
242 writeAttr(into, a.first, a.second,
false,
false);
253 myImpl->myXMLStack.push_back({xmlElement, (int)
myImpl->myValues.size()});
265 if (
myImpl->myMaxDepth == 0) {
269 const bool eof =
myImpl->myXMLStack.empty() || (
myImpl->myHaveRootAttrs &&
myImpl->myXMLStack.size() == 1);
270 if ((
myImpl->myMaxDepth == (
int)
myImpl->myXMLStack.size() || eof) && !
myImpl->myWroteHeader) {
273 if (!
myImpl->myCheckColumns) {
274 WRITE_WARNING(
"Column based formats are still experimental. Autodetection only works for homogeneous output.");
276 bool full =
myImpl->myHeaderFormat ==
"full";
277 if (
myImpl->myHeaderFormat ==
"auto") {
278 std::set<std::string> uniq;
279 for (
const auto& field :
myImpl->mySchema->fields()) {
280 const auto result = uniq.insert(field->name());
281 if (!result.second) {
288 arrow::FieldVector new_fields;
289 for (
const auto& field :
myImpl->mySchema->fields()) {
290 new_fields.push_back(field->WithName(
myImpl->myFullHeader[new_fields.size()]));
292 myImpl->mySchema = arrow::schema(std::move(new_fields),
myImpl->mySchema->metadata());
294 auto arrow_stream = std::make_shared<ArrowOStreamWrapper>(into);
295 std::shared_ptr<parquet::WriterProperties> props = parquet::WriterProperties::Builder().compression(
myImpl->myCompression)->build();
296 myImpl->myParquetWriter = *parquet::arrow::FileWriter::Open(*
myImpl->mySchema, arrow::default_memory_pool(), arrow_stream, props);
297 myImpl->myWroteHeader =
true;
299 bool writeBatch =
false;
300 if (
myImpl->myNeedsWrite) {
302 for (
int i = 0; i < (int)
myImpl->myExpectedAttrs.size(); ++i) {
303 if (
myImpl->myExpectedAttrs.test(i) && !
myImpl->mySeenAttrs.test(i)) {
304 WRITE_ERRORF(
"Incomplete attribute set, '%' is missing. This file format does not support Parquet output yet.",
310 for (
auto& builder :
myImpl->myBuilders) {
311 const auto val = index < (int)
myImpl->myValues.size() ?
myImpl->myValues[index] :
nullptr;
312 arrow::Status s = val ==
nullptr ? builder->AppendNull() : builder->AppendScalar(*val);
314 throw ProcessError(
TLF(
"Error writing attribute '%' (index: %, value: '%'): %",
315 myImpl->myFullHeader[index], index, val ==
nullptr ?
"nullptr" : val->ToString(), s.ToString()));
319 writeBatch =
myImpl->myWroteHeader &&
myImpl->myBuilders.back()->length() >=
myImpl->myBatchSize;
320 myImpl->mySeenAttrs.reset();
321 myImpl->myNeedsWrite =
false;
323 if (writeBatch || (eof && !
myImpl->myBuilders.empty())) {
324 std::vector<std::shared_ptr<arrow::Array> > data;
325 for (
auto& builder :
myImpl->myBuilders) {
326 std::shared_ptr<arrow::Array> column;
327 PARQUET_THROW_NOT_OK(builder->Finish(&column));
328 data.push_back(column);
331 auto batch = arrow::RecordBatch::Make(
myImpl->mySchema, data.back()->length(), data);
332 PARQUET_THROW_NOT_OK(
myImpl->myParquetWriter->WriteRecordBatch(*batch));
334 if (!
myImpl->myXMLStack.empty()) {
335 if ((
int)
myImpl->myValues.size() >
myImpl->myXMLStack.back().second) {
336 myImpl->myValues.resize(
myImpl->myXMLStack.back().second);
338 myImpl->myXMLStack.pop_back();
350 myImpl->myValues.push_back(isNull ?
nullptr : std::make_shared<arrow::DoubleScalar>(val));
353 myImpl->myValues.push_back(isNull ?
nullptr : std::make_shared<arrow::FloatScalar>((
float)val));
362 myImpl->myValues.push_back(isNull ?
nullptr : std::make_shared<arrow::Int32Scalar>(val));
368 assert(!
myImpl->myCheckColumns);
369 if (into.precision() > 2) {
370 myImpl->checkBuilder<std::string, arrow::DoubleBuilder>(attr, arrow::float64);
371 myImpl->myValues.push_back(isNull ?
nullptr : std::make_shared<arrow::DoubleScalar>(val));
373 myImpl->checkBuilder<std::string, arrow::FloatBuilder>(attr, arrow::float32);
374 myImpl->myValues.push_back(isNull ?
nullptr : std::make_shared<arrow::FloatScalar>((
float)val));
381 assert(!
myImpl->myCheckColumns);
382 myImpl->checkBuilder<std::string, arrow::Int32Builder>(attr, arrow::int32);
383 myImpl->myValues.push_back(isNull ?
nullptr : std::make_shared<arrow::Int32Scalar>(val));
391 myImpl->myValues.push_back(std::make_shared<arrow::StringScalar>(val));
397 assert(!
myImpl->myCheckColumns);
398 myImpl->checkBuilder<std::string, arrow::StringBuilder>(attr, arrow::utf8);
399 myImpl->myValues.push_back(std::make_shared<arrow::StringScalar>(val));
407 myImpl->myValues.push_back(
nullptr);
413 assert(!
myImpl->myCheckColumns);
414 myImpl->checkBuilder<std::string, arrow::StringBuilder>(attr, arrow::utf8);
415 myImpl->myValues.push_back(
nullptr);
424 myImpl->myValues.push_back(std::make_shared<arrow::DoubleScalar>(
STEPS2TIME(val)));
433 return myImpl->myWroteHeader;
439 myImpl->myExpectedAttrs = expected;
440 myImpl->myMaxDepth = depth;
441 myImpl->myCheckColumns = expected.any();
#define WRITE_WARNINGF(...)
#define WRITE_ERRORF(...)
#define WRITE_WARNING(msg)
std::string time2string(SUMOTime t, bool humanReadable)
convert SUMOTime to string (independently of global format setting)
SumoXMLTag
Numbers representing SUMO-XML - element names.
std::bitset< 96 > SumoXMLAttrMask
SumoXMLAttr
Numbers representing SUMO-XML - attributes.
@ SUMO_ATTR_SCHEMA_LOCATION
std::string toString(const T &t, std::streamsize accuracy=gPrecision)
bool closed() const override
arrow::Status Close() override
arrow::Status Flush() override
arrow::Status Write(const void *data, int64_t nbytes) override
ArrowOStreamWrapper(std::ostream &out)
arrow::Result< int64_t > Tell() const override