21 #include <unordered_set>
23 #include <boost/filesystem.hpp>
25 #include <ogrsf_frmts.h>
34 namespace import_export {
38 , gdal_dataset_{
nullptr}
40 , array_null_handling_{ArrayNullHandling::kAbortWithWarning} {}
60 #define SCI(x) static_cast<int>(x)
64 static constexpr std::array<const char*, 5>
driver_names = {
"INVALID",
85 {{
true,
false,
false},
89 {
true,
false,
false}}};
92 {{
".csv",
".tsv"}, {
".geojson",
".json"}, {
".geojson",
".json"}, {
".shp"}, {
".fgb"}}};
131 return OFTIntegerList;
143 return OFTStringList;
147 return OFTInteger64List;
156 throw std::runtime_error(
"Column '" + name +
"' has unsupported type '" +
158 file_type_names[
SCI(file_type)] +
"'");
164 const std::string& layer_name,
166 const std::vector<TargetMetaInfo>& column_infos,
182 OGRwkbGeometryType ogr_geometry_type = wkbUnknown;
183 int num_geo_columns = 0;
184 int geo_column_srid = 0;
185 uint32_t num_columns = 0;
186 std::string geo_column_name;
187 for (
auto const& column_info : column_infos) {
188 auto const& type_info = column_info.get_type_info();
189 if (type_info.is_geometry()) {
190 switch (type_info.get_type()) {
192 ogr_geometry_type = wkbPoint;
195 ogr_geometry_type = wkbMultiPoint;
198 ogr_geometry_type = wkbLineString;
201 ogr_geometry_type = wkbMultiLineString;
204 ogr_geometry_type = wkbPolygon;
207 ogr_geometry_type = wkbMultiPolygon;
212 geo_column_srid = type_info.get_output_srid();
213 geo_column_name =
safeColumnName(column_info.get_resname(), num_columns + 1);
216 auto column_name =
safeColumnName(column_info.get_resname(), num_columns + 1);
222 if (num_geo_columns != 1) {
223 throw std::runtime_error(
"File type '" +
225 "' requires exactly one geo column in query results");
229 if (geo_column_srid <= 0) {
230 throw std::runtime_error(
"Geo column '" + geo_column_name +
"' has invalid SRID (" +
232 "). Use ST_SetSRID() in query to override.");
237 auto gdal_driver = GetGDALDriverManager()->GetDriverByName(driver_name);
238 if (gdal_driver ==
nullptr) {
239 throw std::runtime_error(
"Failed to find Driver '" + std::string(driver_name) +
244 auto gdal_file_path{file_path};
245 auto user_file_path{file_path};
250 throw std::runtime_error(
251 "Selected file compression option not yet supported for file type '" +
261 auto remove_file = [](
const std::string&
filename) {
262 if (boost::filesystem::exists(
filename)) {
264 boost::filesystem::remove(
filename);
267 remove_file(file_path);
268 remove_file(user_file_path);
270 LOG(
INFO) <<
"Exporting to file '" << user_file_path <<
"'";
274 gdal_driver->Create(gdal_file_path.c_str(), 0, 0, 0, GDT_Unknown, NULL);
276 throw std::runtime_error(
"Failed to create File '" + file_path +
"'");
280 OGRSpatialReference ogr_spatial_reference;
281 if (ogr_spatial_reference.importFromEPSG(geo_column_srid)) {
282 throw std::runtime_error(
"Failed to create Spatial Reference for SRID " +
285 #if GDAL_VERSION_MAJOR >= 3
286 ogr_spatial_reference.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
291 layer_name.c_str(), &ogr_spatial_reference, ogr_geometry_type, NULL);
293 throw std::runtime_error(
"Failed to create Layer '" + layer_name +
"'");
297 int column_index = 0;
300 for (
auto const& column_info : column_infos) {
301 auto column_name =
safeColumnName(column_info.get_resname(), column_index + 1);
303 auto const& type_info = column_info.get_type_info();
304 if (!type_info.is_geometry()) {
305 OGRFieldDefn field_defn(
308 if (
ogr_layer_->CreateField(&field_defn) != OGRERR_NONE) {
309 throw std::runtime_error(
"Failed to create Field '" + column_name +
"'");
318 }
catch (std::exception& e) {
319 LOG(
INFO) <<
"GDAL Query Export failed to start: " << e.what();
329 const int field_index,
330 OGRFeature* ogr_feature) {
338 auto const point_tv = boost::get<GeoPointTargetValue>(geo_tv->get());
339 auto* coords = point_tv.coords.get();
345 auto const multipoint_tv = boost::get<GeoMultiPointTargetValue>(geo_tv->get());
346 auto* coords = multipoint_tv.coords.get();
352 auto const linestring_tv = boost::get<GeoLineStringTargetValue>(geo_tv->get());
353 auto* coords = linestring_tv.coords.get();
359 auto const multilinestring_tv =
360 boost::get<GeoMultiLineStringTargetValue>(geo_tv->get());
361 auto* coords = multilinestring_tv.coords.get();
363 auto* linestring_sizes = multilinestring_tv.linestring_sizes.get();
364 CHECK(linestring_sizes);
369 auto const polygon_tv = boost::get<GeoPolyTargetValue>(geo_tv->get());
370 auto* coords = polygon_tv.coords.get();
372 auto* ring_sizes = polygon_tv.ring_sizes.get();
378 auto const multipolygon_tv = boost::get<GeoMultiPolyTargetValue>(geo_tv->get());
379 auto* coords = multipolygon_tv.coords.get();
381 auto* ring_sizes = multipolygon_tv.ring_sizes.get();
383 auto* poly_rings = multipolygon_tv.poly_rings.get();
395 const int field_index,
396 OGRFeature* ogr_feature) {
400 auto field_type = ogr_feature->GetFieldDefnRef(field_index)->GetType();
403 if (boost::get<int64_t>(scalar_tv)) {
404 auto int_val = *(boost::get<int64_t>(scalar_tv));
405 bool is_int64 =
false;
433 ogr_feature->SetFieldNull(field_index);
437 ogr_feature->SetField(field_index, str.c_str());
438 }
else if (is_int64) {
440 ogr_feature->SetField(field_index, static_cast<GIntBig>(int_val));
443 ogr_feature->SetField(field_index,
SCI(int_val));
445 }
else if (boost::get<double>(scalar_tv)) {
446 auto real_val = *(boost::get<double>(scalar_tv));
453 ogr_feature->SetFieldNull(field_index);
456 ogr_feature->SetField(field_index, real_val);
458 }
else if (boost::get<float>(scalar_tv)) {
460 auto real_val = *(boost::get<float>(scalar_tv));
462 ogr_feature->SetFieldNull(field_index);
465 ogr_feature->SetField(field_index, real_val);
468 auto s = boost::get<NullableString>(scalar_tv);
469 is_null = !s || boost::get<void*>(s);
471 ogr_feature->SetFieldNull(field_index);
474 auto s_notnull = boost::get<std::string>(s);
476 ogr_feature->SetField(field_index, s_notnull->c_str());
483 const int field_index,
484 OGRFeature* ogr_feature,
485 const std::string& column_name,
490 if (!array_tv->is_initialized()) {
492 ogr_feature->SetFieldNull(field_index);
496 auto const& scalar_tvs = array_tv->get();
498 auto field_type = ogr_feature->GetFieldDefnRef(field_index)->GetType();
504 std::vector<int> int_values;
505 std::vector<GIntBig> int64_values;
506 std::vector<std::string> string_values;
507 std::vector<double> real_values;
508 switch (field_type) {
510 int_values.reserve(scalar_tvs.size());
512 case OFTInteger64List:
513 int64_values.reserve(scalar_tvs.size());
516 real_values.reserve(scalar_tvs.size());
519 string_values.reserve(scalar_tvs.size());
525 bool force_null_to_zero =
529 bool any_null =
false;
530 for (uint32_t i = 0; i < scalar_tvs.size(); i++) {
532 auto const scalar_tv = &scalar_tvs[i];
533 if (boost::get<int64_t>(scalar_tv)) {
534 auto int_val = *(boost::get<int64_t>(scalar_tv));
535 bool is_int64 =
false;
564 string_values.emplace_back(
"");
566 string_values.emplace_back(
569 }
else if (is_int64) {
570 if (is_null && force_null_to_zero) {
571 int64_values.push_back(0);
573 int64_values.push_back(int_val);
576 if (is_null && force_null_to_zero) {
577 int_values.push_back(0);
579 int_values.push_back(int_val);
582 }
else if (boost::get<double>(scalar_tv)) {
583 auto real_val = *(boost::get<double>(scalar_tv));
589 if (is_null && force_null_to_zero) {
590 real_values.push_back(0.0);
592 real_values.push_back(real_val);
594 }
else if (boost::get<float>(scalar_tv)) {
596 auto real_val = *(boost::get<float>(scalar_tv));
598 if (is_null && force_null_to_zero) {
599 real_values.push_back(0.0);
601 real_values.push_back(static_cast<double>(real_val));
604 auto s = boost::get<NullableString>(scalar_tv);
605 is_null = !s || boost::get<void*>(s);
607 string_values.emplace_back(
"");
609 auto s_notnull = boost::get<std::string>(s);
611 string_values.emplace_back(s_notnull->c_str());
619 switch (array_null_handling) {
621 throw std::runtime_error(
622 "Found individual nulls in Array Column '" + column_name +
"' of type '" +
624 "'. Use 'array_null_handling' Export Option to specify behaviour.");
626 ogr_feature->SetFieldNull(field_index);
634 switch (field_type) {
636 ogr_feature->SetField(field_index, int_values.size(), int_values.data());
638 case OFTInteger64List:
639 ogr_feature->SetField(field_index, int64_values.size(), int64_values.data());
642 ogr_feature->SetField(field_index, real_values.size(), real_values.data());
644 case OFTStringList: {
645 std::vector<const char*> raw_strings;
646 raw_strings.reserve(string_values.size() + 1);
647 for (
auto const& string_value : string_values) {
648 raw_strings.push_back(string_value.c_str());
650 raw_strings.push_back(
nullptr);
651 ogr_feature->SetField(field_index, raw_strings.data());
661 const std::vector<AggregatedResult>& query_results) {
663 for (
auto const& agg_result : query_results) {
664 auto results = agg_result.rs;
665 auto const& targets = agg_result.targets_meta;
671 auto const crt_row = results->getNextRow(
true,
true);
672 if (crt_row.empty()) {
677 auto ogr_feature = OGRFeature::CreateFeature(
ogr_layer_->GetLayerDefn());
682 OGRFeature::DestroyFeature(ogr_feature);
685 for (
size_t i = 0; i < results->colCount(); ++i) {
686 auto const tv = crt_row[i];
687 auto const& ti = targets[i].get_type_info();
688 auto const column_name =
safeColumnName(targets[i].get_resname(), i + 1);
692 auto const scalar_tv = boost::get<ScalarTargetValue>(&tv);
696 auto const array_tv = boost::get<ArrayTargetValue>(&tv);
705 auto const geo_tv = boost::get<GeoTargetValue>(&tv);
706 if (geo_tv && geo_tv->is_initialized()) {
709 ogr_feature->SetGeometry(
nullptr);
716 if (
ogr_layer_->CreateFeature(ogr_feature) != OGRERR_NONE) {
717 throw std::runtime_error(
"Failed to create Feature");
721 }
catch (std::exception& e) {
722 LOG(
INFO) <<
"GDAL Query Export failed: " << e.what();
HOST DEVICE SQLTypes get_subtype() const
static constexpr std::array< const char *, 5 > file_type_names
const FileType file_type_
const OGRGeometry * getOGRGeometry() const
static constexpr std::array< std::array< bool, 3 >, 5 > compression_implemented
std::string convert_temporal_to_iso_format(const SQLTypeInfo &type_info, int64_t unix_time)
void insert_array_column(const ArrayTargetValue *array_tv, const SQLTypeInfo &ti, const int field_index, OGRFeature *ogr_feature, const std::string &column_name, QueryExporter::ArrayNullHandling array_null_handling)
std::string safeColumnName(const std::string &resname, const int column_index)
~QueryExporterGDAL() override
void beginExport(const std::string &file_path, const std::string &layer_name, const CopyParams ©_params, const std::vector< TargetMetaInfo > &column_infos, const FileCompression file_compression, const ArrayNullHandling array_null_handling) final
HOST DEVICE SQLTypes get_type() const
static std::array< std::unordered_set< std::string >, 5 > file_type_valid_extensions
ArrayNullHandling array_null_handling_
static constexpr std::array< const char *, 3 > compression_prefix
OGRFieldType sql_type_info_to_ogr_field_type(const std::string &name, const SQLTypeInfo &type_info, const QueryExporter::FileType file_type)
CONSTEXPR DEVICE bool is_null(const T &value)
boost::optional< std::vector< ScalarTargetValue >> ArrayTargetValue
boost::optional< boost::variant< GeoPointTargetValue, GeoMultiPointTargetValue, GeoLineStringTargetValue, GeoMultiLineStringTargetValue, GeoPolyTargetValue, GeoMultiPolyTargetValue >> GeoTargetValue
void insert_geo_column(const GeoTargetValue *geo_tv, const SQLTypeInfo &ti, const int field_index, OGRFeature *ogr_feature)
void exportResults(const std::vector< AggregatedResult > &query_results) final
static constexpr std::array< const char *, 3 > compression_suffix
void insert_scalar_column(const ScalarTargetValue *scalar_tv, const SQLTypeInfo &ti, const int field_index, OGRFeature *ogr_feature)
std::string get_type_name() const
std::vector< int > field_indices_
std::string filename(char const *path)
QueryExporterGDAL()=delete
Basic constructors and methods of the row set interface.
SQLTypeInfo get_elem_type() const
void validateFileExtensions(const std::string &file_path, const std::string &file_type, const std::unordered_set< std::string > &valid_extensions) const
static constexpr std::array< const char *, 5 > driver_names
boost::variant< int64_t, double, float, NullableString > ScalarTargetValue
GDALDataset * gdal_dataset_