LCOV - code coverage report
Current view: top level - apps - gdalalg_vector_partition.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 653 697 93.7 %
Date: 2026-06-23 16:35:19 Functions: 17 17 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  "partition" step of "vector pipeline"
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "gdalalg_vector_partition.h"
      14             : 
      15             : #include "cpl_vsi.h"
      16             : #include "cpl_mem_cache.h"
      17             : #include "ogr_p.h"
      18             : 
      19             : #include <algorithm>
      20             : #include <set>
      21             : #include <string_view>
      22             : 
      23             : #ifndef _
      24             : #define _(x) (x)
      25             : #endif
      26             : 
      27             : //! @cond Doxygen_Suppress
      28             : 
      29             : constexpr int DIRECTORY_CREATION_MODE = 0755;
      30             : 
      31             : constexpr const char *NULL_MARKER = "__HIVE_DEFAULT_PARTITION__";
      32             : 
      33             : constexpr const char *DEFAULT_PATTERN_HIVE = "part_%010d";
      34             : constexpr const char *DEFAULT_PATTERN_FLAT_NO_FIELD = "{LAYER_NAME}_%010d";
      35             : constexpr const char *DEFAULT_PATTERN_FLAT = "{LAYER_NAME}_{FIELD_VALUE}_%010d";
      36             : 
      37             : constexpr char DIGIT_ZERO = '0';
      38             : 
      39             : /************************************************************************/
      40             : /*                       GetConstructorOptions()                        */
      41             : /************************************************************************/
      42             : 
      43             : /* static */
      44             : GDALVectorPartitionAlgorithm::ConstructorOptions
      45         131 : GDALVectorPartitionAlgorithm::GetConstructorOptions(bool standaloneStep)
      46             : {
      47         131 :     GDALVectorPartitionAlgorithm::ConstructorOptions options;
      48         131 :     options.SetStandaloneStep(standaloneStep);
      49         131 :     options.SetAddInputLayerNameArgument(false);
      50         131 :     options.SetAddDefaultArguments(false);
      51         131 :     return options;
      52             : }
      53             : 
      54             : /************************************************************************/
      55             : /*     GDALVectorPartitionAlgorithm::GDALVectorPartitionAlgorithm()     */
      56             : /************************************************************************/
      57             : 
      58         131 : GDALVectorPartitionAlgorithm::GDALVectorPartitionAlgorithm(bool standaloneStep)
      59             :     : GDALVectorPipelineStepAlgorithm(NAME, DESCRIPTION, HELP_URL,
      60         131 :                                       GetConstructorOptions(standaloneStep))
      61             : {
      62         131 :     if (standaloneStep)
      63             :     {
      64          93 :         AddVectorInputArgs(false);
      65          93 :         AddProgressArg();
      66             :     }
      67             :     else
      68             :     {
      69          38 :         AddVectorHiddenInputDatasetArg();
      70             :     }
      71             : 
      72         262 :     AddArg(GDAL_ARG_NAME_OUTPUT, 'o', _("Output directory"), &m_output)
      73         131 :         .SetRequired()
      74         131 :         .SetIsInput()
      75         131 :         .SetMinCharCount(1)
      76         131 :         .SetPositional();
      77             : 
      78         131 :     constexpr const char *OVERWRITE_APPEND_EXCLUSION_GROUP = "overwrite-append";
      79         131 :     AddOverwriteArg(&m_overwrite)
      80         131 :         .SetMutualExclusionGroup(OVERWRITE_APPEND_EXCLUSION_GROUP);
      81         131 :     AddAppendLayerArg(&m_appendLayer)
      82         131 :         .SetMutualExclusionGroup(OVERWRITE_APPEND_EXCLUSION_GROUP);
      83         131 :     AddUpdateArg(&m_update).SetHidden();
      84             : 
      85             :     AddOutputFormatArg(&m_format, /* bStreamAllowed = */ false,
      86         131 :                        /* bGDALGAllowed = */ false)
      87             :         .AddMetadataItem(GAAMDI_REQUIRED_CAPABILITIES,
      88         393 :                          {GDAL_DCAP_VECTOR, GDAL_DCAP_CREATE});
      89         131 :     AddCreationOptionsArg(&m_creationOptions);
      90         131 :     AddLayerCreationOptionsArg(&m_layerCreationOptions);
      91             : 
      92             :     auto &fieldNameArg = AddArg(
      93             :         "field", 0, _("Attribute or geometry field(s) on which to partition"),
      94         131 :         &m_fields);
      95         262 :     SetAutoCompleteFunctionForFieldName(fieldNameArg, nullptr,
      96             :                                         /* attributeFields = */ true,
      97             :                                         /* geometryFields = */ true,
      98         131 :                                         m_inputDataset);
      99         262 :     AddArg("scheme", 0, _("Partitioning scheme"), &m_scheme)
     100         131 :         .SetChoices(SCHEME_HIVE, SCHEME_FLAT)
     101         131 :         .SetDefault(m_scheme);
     102             :     AddArg("pattern", 0,
     103             :            _("Filename pattern ('part_%010d' for scheme=hive, "
     104             :              "'{LAYER_NAME}_{FIELD_VALUE}_%010d' for scheme=flat)"),
     105         262 :            &m_pattern)
     106         131 :         .SetMinCharCount(1)
     107             :         .AddValidationAction(
     108          72 :             [this]()
     109             :             {
     110           8 :                 if (!m_pattern.empty())
     111             :                 {
     112           8 :                     const auto nPercentPos = m_pattern.find('%');
     113           8 :                     if (nPercentPos == std::string::npos)
     114             :                     {
     115           1 :                         ReportError(CE_Failure, CPLE_IllegalArg, "%s",
     116             :                                     "Missing '%' character in pattern");
     117           1 :                         return false;
     118             :                     }
     119          13 :                     if (nPercentPos + 1 < m_pattern.size() &&
     120           6 :                         m_pattern.find('%', nPercentPos + 1) !=
     121             :                             std::string::npos)
     122             :                     {
     123           1 :                         ReportError(
     124             :                             CE_Failure, CPLE_IllegalArg, "%s",
     125             :                             "A single '%' character is expected in pattern");
     126           1 :                         return false;
     127             :                     }
     128           6 :                     bool percentFound = false;
     129           9 :                     for (size_t i = nPercentPos + 1; i < m_pattern.size(); ++i)
     130             :                     {
     131           7 :                         if (m_pattern[i] >= DIGIT_ZERO && m_pattern[i] <= '9')
     132             :                         {
     133             :                             // ok
     134             :                         }
     135           4 :                         else if (m_pattern[i] == 'd')
     136             :                         {
     137           3 :                             percentFound = true;
     138           3 :                             break;
     139             :                         }
     140             :                         else
     141             :                         {
     142           1 :                             break;
     143             :                         }
     144             :                     }
     145           6 :                     if (!percentFound)
     146             :                     {
     147           3 :                         ReportError(
     148             :                             CE_Failure, CPLE_IllegalArg, "%s",
     149             :                             "pattern value must include a single "
     150             :                             "'%[0]?[1-9]?[0]?d' part number specification");
     151           3 :                         return false;
     152             :                     }
     153           3 :                     m_partDigitCount =
     154           3 :                         atoi(m_pattern.c_str() + nPercentPos + 1);
     155           3 :                     if (m_partDigitCount > 10)
     156             :                     {
     157           1 :                         ReportError(CE_Failure, CPLE_IllegalArg,
     158             :                                     "Number of digits in part number "
     159             :                                     "specification should be in [1,10] range");
     160           1 :                         return false;
     161             :                     }
     162           2 :                     m_partDigitLeadingZeroes =
     163           2 :                         m_pattern[nPercentPos + 1] == DIGIT_ZERO;
     164             :                 }
     165           2 :                 return true;
     166         131 :             });
     167             :     AddArg("feature-limit", 0, _("Maximum number of features per file"),
     168         262 :            &m_featureLimit)
     169         131 :         .SetMinValueExcluded(0);
     170             :     AddArg("max-file-size", 0,
     171             :            _("Maximum file size (MB or GB suffix can be used)"),
     172         262 :            &m_maxFileSizeStr)
     173             :         .AddValidationAction(
     174          24 :             [this]()
     175             :             {
     176             :                 bool ok;
     177             :                 {
     178           6 :                     CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
     179          12 :                     ok = CPLParseMemorySize(m_maxFileSizeStr.c_str(),
     180             :                                             &m_maxFileSize,
     181          11 :                                             nullptr) == CE_None &&
     182           5 :                          m_maxFileSize > 0;
     183             :                 }
     184           6 :                 if (!ok)
     185             :                 {
     186           1 :                     ReportError(CE_Failure, CPLE_IllegalArg,
     187             :                                 "Invalid value for max-file-size");
     188           1 :                     return false;
     189             :                 }
     190           5 :                 else if (m_maxFileSize < 1024 * 1024)
     191             :                 {
     192           1 :                     ReportError(CE_Failure, CPLE_IllegalArg,
     193             :                                 "max-file-size should be at least one MB");
     194           1 :                     return false;
     195             :                 }
     196           4 :                 return true;
     197         131 :             });
     198             :     AddArg("omit-partitioned-field", 0,
     199             :            _("Whether to omit partitioned fields from target layer definition"),
     200         131 :            &m_omitPartitionedFields);
     201             :     AddArg("skip-errors", 0, _("Skip errors when writing features"),
     202         131 :            &m_skipErrors);
     203             : 
     204             :     // Hidden for now
     205             : 
     206             :     AddArg("max-cache-size", 0,
     207             :            _("Maximum number of datasets simultaneously opened"),
     208         262 :            &m_maxCacheSize)
     209         131 :         .SetMinValueIncluded(0)  // 0 = unlimited
     210         131 :         .SetDefault(m_maxCacheSize)
     211         131 :         .SetHidden();
     212             : 
     213             :     AddArg("transaction-size", 0,
     214         262 :            _("Maximum number of features per transaction"), &m_transactionSize)
     215         131 :         .SetMinValueIncluded(1)
     216         131 :         .SetDefault(m_transactionSize)
     217         131 :         .SetHidden();
     218             : 
     219         131 :     AddValidationAction(
     220          53 :         [this]()
     221             :         {
     222          48 :             if (m_fields.empty() && m_featureLimit == 0 && m_maxFileSize == 0)
     223             :             {
     224           1 :                 ReportError(
     225             :                     CE_Failure, CPLE_IllegalArg,
     226             :                     "When 'fields' argument is not specified, "
     227             :                     "'feature-limit' and/or 'max-file-size' must be specified");
     228           1 :                 return false;
     229             :             }
     230          47 :             return true;
     231             :         });
     232         131 : }
     233             : 
     234             : /************************************************************************/
     235             : /*                           PercentEncode()                            */
     236             : /************************************************************************/
     237             : 
     238       20305 : static void PercentEncode(std::string &out, const std::string_view &s)
     239             : {
     240       92085 :     for (unsigned char c : s)
     241             :     {
     242       71780 :         if (c > 32 && c <= 127 && c != ':' && c != '/' && c != '\\' &&
     243       71733 :             c != '>' && c != '%' && c != '=')
     244             :         {
     245       71733 :             out += c;
     246             :         }
     247             :         else
     248             :         {
     249          47 :             out += CPLSPrintf("%%%02X", c);
     250             :         }
     251             :     }
     252       20305 : }
     253             : 
     254       10237 : static std::string PercentEncode(const std::string_view &s)
     255             : {
     256       10237 :     std::string out;
     257       10237 :     PercentEncode(out, s);
     258       10237 :     return out;
     259             : }
     260             : 
     261             : /************************************************************************/
     262             : /*                      GetEstimatedFeatureSize()                       */
     263             : /************************************************************************/
     264             : 
     265       10000 : static size_t GetEstimatedFeatureSize(
     266             :     const OGRFeature *poFeature, const std::vector<bool> &abPartitionedFields,
     267             :     const bool omitPartitionedFields,
     268             :     const std::vector<OGRFieldType> &aeSrcFieldTypes, bool bIsBinary)
     269             : {
     270       10000 :     size_t nSize = 16;
     271       10000 :     const int nFieldCount = poFeature->GetFieldCount();
     272       10000 :     nSize += 4 * nFieldCount;
     273      110000 :     for (int i = 0; i < nFieldCount; ++i)
     274             :     {
     275      100000 :         if (!(omitPartitionedFields && abPartitionedFields[i]))
     276             :         {
     277      100000 :             switch (aeSrcFieldTypes[i])
     278             :             {
     279       10000 :                 case OFTInteger:
     280       10000 :                     nSize += bIsBinary ? sizeof(int) : 11;
     281       10000 :                     break;
     282       10000 :                 case OFTInteger64:
     283       10000 :                     nSize += bIsBinary ? sizeof(int64_t) : 21;
     284       10000 :                     break;
     285       10000 :                 case OFTReal:
     286             :                     // Decimal representation
     287       10000 :                     nSize += bIsBinary ? sizeof(double) : 15;
     288       10000 :                     break;
     289       10000 :                 case OFTString:
     290       10000 :                     nSize += 4 + strlen(poFeature->GetFieldAsStringUnsafe(i));
     291       10000 :                     break;
     292       10000 :                 case OFTBinary:
     293             :                 {
     294       10000 :                     int nCount = 0;
     295       10000 :                     CPL_IGNORE_RET_VAL(poFeature->GetFieldAsBinary(i, &nCount));
     296       10000 :                     nSize += 4 + nCount;
     297       10000 :                     break;
     298             :                 }
     299        5000 :                 case OFTIntegerList:
     300             :                 {
     301        5000 :                     int nCount = 0;
     302        5000 :                     CPL_IGNORE_RET_VAL(
     303        5000 :                         poFeature->GetFieldAsIntegerList(i, &nCount));
     304        5000 :                     nSize += 4 + (bIsBinary ? sizeof(int) : 11) * nCount;
     305        5000 :                     break;
     306             :                 }
     307        5000 :                 case OFTInteger64List:
     308             :                 {
     309        5000 :                     int nCount = 0;
     310        5000 :                     CPL_IGNORE_RET_VAL(
     311        5000 :                         poFeature->GetFieldAsInteger64List(i, &nCount));
     312        5000 :                     nSize += 4 + (bIsBinary ? sizeof(int64_t) : 21) * nCount;
     313        5000 :                     break;
     314             :                 }
     315        5000 :                 case OFTRealList:
     316             :                 {
     317        5000 :                     int nCount = 0;
     318        5000 :                     CPL_IGNORE_RET_VAL(
     319        5000 :                         poFeature->GetFieldAsDoubleList(i, &nCount));
     320        5000 :                     nSize += 4 + (bIsBinary ? sizeof(double) : 15) * nCount;
     321        5000 :                     break;
     322             :                 }
     323        5000 :                 case OFTStringList:
     324             :                 {
     325        5000 :                     CSLConstList papszIter = poFeature->GetFieldAsStringList(i);
     326        5000 :                     nSize += 4;
     327       15000 :                     for (; papszIter && *papszIter; ++papszIter)
     328       10000 :                         nSize += 4 + strlen(*papszIter);
     329        5000 :                     break;
     330             :                 }
     331       10000 :                 case OFTTime:
     332             :                     // Decimal representation
     333       10000 :                     nSize += 4 + sizeof("HH:MM:SS.sss");
     334       10000 :                     break;
     335       10000 :                 case OFTDate:
     336             :                     // Decimal representation
     337       10000 :                     nSize += 4 + sizeof("YYYY-MM-DD");
     338       10000 :                     break;
     339       10000 :                 case OFTDateTime:
     340             :                     // Decimal representation
     341       10000 :                     nSize += 4 + sizeof("YYYY-MM-DDTHH:MM:SS.sss+HH:MM");
     342       10000 :                     break;
     343           0 :                 case OFTWideString:
     344             :                 case OFTWideStringList:
     345           0 :                     break;
     346             :             }
     347             :         }
     348             :     }
     349             : 
     350       10000 :     const int nGeomFieldCount = poFeature->GetGeomFieldCount();
     351       10000 :     nSize += 4 * nGeomFieldCount;
     352       20000 :     for (int i = 0; i < nGeomFieldCount; ++i)
     353             :     {
     354       10000 :         const auto poGeom = poFeature->GetGeomFieldRef(i);
     355       10000 :         if (poGeom)
     356       10000 :             nSize += poGeom->WkbSize();
     357             :     }
     358             : 
     359       10000 :     return nSize;
     360             : }
     361             : 
     362             : /************************************************************************/
     363             : /*                       GetCurrentOutputLayer()                        */
     364             : /************************************************************************/
     365             : 
     366             : constexpr int MIN_FILE_SIZE = 65536;
     367             : 
     368             : namespace
     369             : {
     370             : struct Layer
     371             : {
     372             :     bool bUseTransactions = false;
     373             :     std::unique_ptr<GDALDataset> poDS{};
     374             :     OGRLayer *poLayer = nullptr;
     375             :     GIntBig nFeatureCount = 0;
     376             :     int nFileCounter = 1;
     377             :     GIntBig nFileSize = MIN_FILE_SIZE;
     378             : 
     379         151 :     ~Layer()
     380         151 :     {
     381         151 :         if (poDS)
     382             :         {
     383          87 :             CPL_IGNORE_RET_VAL(poDS->CommitTransaction());
     384             :         }
     385         151 :     }
     386             : };
     387             : }  // namespace
     388             : 
     389       10115 : static bool GetCurrentOutputLayer(
     390             :     GDALAlgorithm *const alg, const OGRFeatureDefn *const poSrcFeatureDefn,
     391             :     OGRLayer *const poSrcLayer, const std::string &osKey,
     392             :     const std::vector<OGRwkbGeometryType> &aeGeomTypes,
     393             :     const std::string &osLayerDir, const std::string &osScheme,
     394             :     const std::string &osPatternIn, bool partDigitLeadingZeroes,
     395             :     size_t partDigitCount, const int featureLimit, const GIntBig maxFileSize,
     396             :     const bool omitPartitionedFields,
     397             :     const std::vector<bool> &abPartitionedFields,
     398             :     const std::vector<bool> &abPartitionedGeomFields, const char *pszExtension,
     399             :     GDALDriver *const poOutDriver, const CPLStringList &datasetCreationOptions,
     400             :     const CPLStringList &layerCreationOptions,
     401             :     const OGRFeatureDefn *const poFeatureDefnWithoutPartitionedFields,
     402             :     const int nSpatialIndexPerFeatureConstant,
     403             :     const int nSpatialIndexPerLog2FeatureCountConstant, bool bUseTransactions,
     404             :     lru11::Cache<std::string, std::shared_ptr<Layer>> &oCacheOutputLayer,
     405             :     std::shared_ptr<Layer> &outputLayer)
     406             : {
     407             :     const std::string osPattern =
     408       10115 :         !osPatternIn.empty() ? osPatternIn
     409       10111 :         : osScheme == GDALVectorPartitionAlgorithm::SCHEME_HIVE
     410             :             ? DEFAULT_PATTERN_HIVE
     411           8 :         : osKey.empty() ? DEFAULT_PATTERN_FLAT_NO_FIELD
     412       20238 :                         : DEFAULT_PATTERN_FLAT;
     413             : 
     414       10115 :     bool bLimitReached = false;
     415       10115 :     bool bOpenOrCreateNewFile = true;
     416       10115 :     if (oCacheOutputLayer.tryGet(osKey, outputLayer))
     417             :     {
     418       10025 :         if (featureLimit > 0 && outputLayer->nFeatureCount >= featureLimit)
     419             :         {
     420           4 :             bLimitReached = true;
     421             :         }
     422       20019 :         else if (maxFileSize > 0 &&
     423       19996 :                  outputLayer->nFileSize +
     424             :                          (nSpatialIndexPerFeatureConstant > 0
     425        9998 :                               ? (outputLayer->nFeatureCount *
     426        9998 :                                      nSpatialIndexPerFeatureConstant +
     427        4999 :                                  static_cast<int>(std::ceil(
     428        4999 :                                      log2(outputLayer->nFeatureCount)))) *
     429        4999 :                                     nSpatialIndexPerLog2FeatureCountConstant
     430             :                               : 0) >=
     431             :                      maxFileSize)
     432             :         {
     433           2 :             bLimitReached = true;
     434             :         }
     435             :         else
     436             :         {
     437       10019 :             bOpenOrCreateNewFile = false;
     438             :         }
     439             :     }
     440             :     else
     441             :     {
     442          90 :         outputLayer = std::make_unique<Layer>();
     443          90 :         outputLayer->bUseTransactions = bUseTransactions;
     444             :     }
     445             : 
     446       20246 :     const auto SubstituteVariables = [&osKey, poSrcLayer](const std::string &s)
     447             :     {
     448       10119 :         CPLString ret(s);
     449             :         ret.replaceAll("{LAYER_NAME}",
     450       10119 :                        PercentEncode(poSrcLayer->GetDescription()));
     451             : 
     452       10119 :         if (ret.find("{FIELD_VALUE}") != std::string::npos)
     453             :         {
     454          16 :             std::string fieldValue;
     455             :             const CPLStringList aosTokens(
     456           8 :                 CSLTokenizeString2(osKey.c_str(), "/", 0));
     457          16 :             for (int i = 0; i < aosTokens.size(); ++i)
     458             :             {
     459             :                 const CPLStringList aosFieldNameValue(
     460           8 :                     CSLTokenizeString2(aosTokens[i], "=", 0));
     461           8 :                 if (!fieldValue.empty())
     462           0 :                     fieldValue += '_';
     463             :                 fieldValue +=
     464           8 :                     aosFieldNameValue.size() == 2
     465          16 :                         ? (strcmp(aosFieldNameValue[1], NULL_MARKER) == 0
     466             :                                ? std::string("__NULL__")
     467             :                                : aosFieldNameValue[1])
     468           8 :                         : std::string("__EMPTY__");
     469             :             }
     470           8 :             ret.replaceAll("{FIELD_VALUE}", fieldValue);
     471             :         }
     472       10119 :         return ret;
     473       10115 :     };
     474             : 
     475       10115 :     const auto nPercentPos = osPattern.find('%');
     476       10115 :     CPLAssert(nPercentPos !=
     477             :               std::string::npos);  // checked by validation action
     478             :     const std::string osPatternPrefix =
     479       30345 :         SubstituteVariables(osPattern.substr(0, nPercentPos));
     480       10115 :     const auto nAfterDPos = osPattern.find('d', nPercentPos + 1) + 1;
     481             :     const std::string osPatternSuffix =
     482       10115 :         nAfterDPos < osPattern.size()
     483       10123 :             ? SubstituteVariables(osPattern.substr(nAfterDPos))
     484       20234 :             : std::string();
     485             : 
     486          99 :     const auto GetBasenameFromCounter = [partDigitCount, partDigitLeadingZeroes,
     487             :                                          &osPatternPrefix,
     488         489 :                                          &osPatternSuffix](int nCounter)
     489             :     {
     490         198 :         const std::string sCounter(CPLSPrintf("%d", nCounter));
     491          99 :         std::string s(osPatternPrefix);
     492          99 :         if (sCounter.size() < partDigitCount)
     493             :         {
     494         192 :             s += std::string(partDigitCount - sCounter.size(),
     495          96 :                              partDigitLeadingZeroes ? DIGIT_ZERO : ' ');
     496             :         }
     497          99 :         s += sCounter;
     498          99 :         s += osPatternSuffix;
     499         198 :         return s;
     500       10115 :     };
     501             : 
     502       10115 :     if (bOpenOrCreateNewFile)
     503             :     {
     504             :         std::string osDatasetDir =
     505          96 :             osScheme == GDALVectorPartitionAlgorithm::SCHEME_HIVE
     506             :                 ? CPLFormFilenameSafe(osLayerDir.c_str(), osKey.c_str(),
     507             :                                       nullptr)
     508          96 :                 : osLayerDir;
     509          96 :         outputLayer->nFeatureCount = 0;
     510             : 
     511          96 :         bool bCreateNewFile = true;
     512          96 :         if (bLimitReached)
     513             :         {
     514           6 :             ++outputLayer->nFileCounter;
     515             :         }
     516             :         else
     517             :         {
     518          90 :             outputLayer->nFileCounter = 1;
     519             : 
     520             :             VSIStatBufL sStat;
     521          90 :             if (VSIStatL(osDatasetDir.c_str(), &sStat) != 0)
     522             :             {
     523          71 :                 if (VSIMkdirRecursive(osDatasetDir.c_str(),
     524          71 :                                       DIRECTORY_CREATION_MODE) != 0)
     525             :                 {
     526           0 :                     alg->ReportError(CE_Failure, CPLE_AppDefined,
     527             :                                      "Cannot create directory '%s'",
     528             :                                      osDatasetDir.c_str());
     529           3 :                     return false;
     530             :                 }
     531             :             }
     532             : 
     533          90 :             int nMaxCounter = 0;
     534             :             std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
     535          90 :                 VSIOpenDir(osDatasetDir.c_str(), 0, nullptr), VSICloseDir);
     536          90 :             if (psDir)
     537             :             {
     538         110 :                 while (const auto *psEntry = VSIGetNextDirEntry(psDir.get()))
     539             :                 {
     540             :                     const std::string osName(
     541          40 :                         CPLGetBasenameSafe(psEntry->pszName));
     542          32 :                     if (cpl::starts_with(osName, osPatternPrefix) &&
     543          12 :                         cpl::ends_with(osName, osPatternSuffix))
     544             :                     {
     545          10 :                         nMaxCounter = std::max(
     546             :                             nMaxCounter,
     547          10 :                             atoi(osName
     548          20 :                                      .substr(osPatternPrefix.size(),
     549          10 :                                              osName.size() -
     550          10 :                                                  osPatternPrefix.size() -
     551          10 :                                                  osPatternSuffix.size())
     552          10 :                                      .c_str()));
     553             :                     }
     554          20 :                 }
     555             :             }
     556             : 
     557          90 :             if (nMaxCounter > 0)
     558             :             {
     559           9 :                 outputLayer->nFileCounter = nMaxCounter;
     560             : 
     561             :                 const std::string osFilename = CPLFormFilenameSafe(
     562             :                     osDatasetDir.c_str(),
     563           9 :                     GetBasenameFromCounter(nMaxCounter).c_str(), pszExtension);
     564             :                 auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
     565             :                     osFilename.c_str(),
     566           9 :                     GDAL_OF_VECTOR | GDAL_OF_UPDATE | GDAL_OF_VERBOSE_ERROR));
     567           9 :                 if (!poDS)
     568           1 :                     return false;
     569           8 :                 auto poDstLayer = poDS->GetLayer(0);
     570           8 :                 if (!poDstLayer)
     571             :                 {
     572           1 :                     alg->ReportError(CE_Failure, CPLE_AppDefined,
     573             :                                      "No layer in %s", osFilename.c_str());
     574           1 :                     return false;
     575             :                 }
     576             : 
     577             :                 // Check if the existing output layer has the expected layer
     578             :                 // definition
     579           7 :                 const auto poRefFeatureDefn =
     580             :                     poFeatureDefnWithoutPartitionedFields
     581             :                         ? poFeatureDefnWithoutPartitionedFields
     582             :                         : poSrcFeatureDefn;
     583           7 :                 const auto poDstFeatureDefn = poDstLayer->GetLayerDefn();
     584           7 :                 bool bSameDefinition = (poDstFeatureDefn->GetFieldCount() ==
     585           7 :                                         poRefFeatureDefn->GetFieldCount());
     586           7 :                 for (int i = 0;
     587          31 :                      bSameDefinition && i < poRefFeatureDefn->GetFieldCount();
     588             :                      ++i)
     589             :                 {
     590             :                     const auto poRefFieldDefn =
     591          24 :                         poRefFeatureDefn->GetFieldDefn(i);
     592             :                     const auto poDstFieldDefn =
     593          24 :                         poDstFeatureDefn->GetFieldDefn(i);
     594          24 :                     bSameDefinition =
     595          24 :                         EQUAL(poRefFieldDefn->GetNameRef(),
     596          48 :                               poDstFieldDefn->GetNameRef()) &&
     597          24 :                         poRefFieldDefn->GetType() == poDstFieldDefn->GetType();
     598             :                 }
     599           7 :                 bSameDefinition =
     600          13 :                     bSameDefinition && (poDstFeatureDefn->GetGeomFieldCount() ==
     601           6 :                                         poRefFeatureDefn->GetGeomFieldCount());
     602          21 :                 for (int i = 0; bSameDefinition &&
     603          10 :                                 i < poRefFeatureDefn->GetGeomFieldCount();
     604             :                      ++i)
     605             :                 {
     606             :                     const auto poRefFieldDefn =
     607           4 :                         poRefFeatureDefn->GetGeomFieldDefn(i);
     608             :                     const auto poDstFieldDefn =
     609           4 :                         poDstFeatureDefn->GetGeomFieldDefn(i);
     610           4 :                     bSameDefinition =
     611           4 :                         (poRefFeatureDefn->GetGeomFieldCount() == 1 ||
     612           0 :                          EQUAL(poRefFieldDefn->GetNameRef(),
     613             :                                poDstFieldDefn->GetNameRef()));
     614             :                 }
     615             : 
     616           7 :                 if (!bSameDefinition)
     617             :                 {
     618           1 :                     alg->ReportError(CE_Failure, CPLE_AppDefined,
     619             :                                      "%s does not have the same feature "
     620             :                                      "definition as the source layer",
     621             :                                      osFilename.c_str());
     622           1 :                     return false;
     623             :                 }
     624             : 
     625           6 :                 if (VSIStatL(osFilename.c_str(), &sStat) == 0)
     626             :                 {
     627           6 :                     outputLayer->nFileSize = sStat.st_size;
     628             :                 }
     629             : 
     630           6 :                 GIntBig nFeatureCount = 0;
     631           9 :                 if (((featureLimit == 0 ||
     632           3 :                       (nFeatureCount = poDstLayer->GetFeatureCount(true)) <
     633           9 :                           featureLimit)) &&
     634           0 :                     (maxFileSize == 0 || outputLayer->nFileSize < maxFileSize))
     635             :                 {
     636           3 :                     bCreateNewFile = false;
     637           3 :                     outputLayer->poDS = std::move(poDS);
     638           3 :                     outputLayer->poLayer = poDstLayer;
     639           3 :                     outputLayer->nFeatureCount = nFeatureCount;
     640             : 
     641           3 :                     if (bUseTransactions)
     642             :                     {
     643           3 :                         if (outputLayer->poDS->StartTransaction() !=
     644             :                             OGRERR_NONE)
     645             :                         {
     646           0 :                             return false;
     647             :                         }
     648             :                     }
     649             :                 }
     650             :                 else
     651             :                 {
     652           3 :                     ++outputLayer->nFileCounter;
     653             :                 }
     654             :             }
     655             :         }
     656             : 
     657          93 :         if (bCreateNewFile)
     658             :         {
     659          90 :             outputLayer->nFileSize = MIN_FILE_SIZE;
     660             : 
     661          96 :             if (bUseTransactions && outputLayer->poDS &&
     662           6 :                 outputLayer->poDS->CommitTransaction() != OGRERR_NONE)
     663             :             {
     664           3 :                 return false;
     665             :             }
     666             : 
     667             :             const std::string osFilename = CPLFormFilenameSafe(
     668             :                 osDatasetDir.c_str(),
     669          90 :                 GetBasenameFromCounter(outputLayer->nFileCounter).c_str(),
     670          90 :                 pszExtension);
     671          90 :             outputLayer->poDS.reset(
     672             :                 poOutDriver->Create(osFilename.c_str(), 0, 0, 0, GDT_Unknown,
     673             :                                     datasetCreationOptions.List()));
     674          90 :             if (!outputLayer->poDS)
     675             :             {
     676           0 :                 alg->ReportError(CE_Failure, CPLE_AppDefined,
     677             :                                  "Cannot create dataset '%s'",
     678             :                                  osFilename.c_str());
     679           0 :                 return false;
     680             :             }
     681             : 
     682          90 :             CPLStringList modLayerCreationOptions(layerCreationOptions);
     683          90 :             const char *pszSrcFIDColumn = poSrcLayer->GetFIDColumn();
     684          90 :             if (pszSrcFIDColumn[0])
     685             :             {
     686         114 :                 const char *pszLCO = poOutDriver->GetMetadataItem(
     687          57 :                     GDAL_DS_LAYER_CREATIONOPTIONLIST);
     688         104 :                 if (pszLCO && strstr(pszLCO, "'FID'") &&
     689          47 :                     layerCreationOptions.FetchNameValue("FID") == nullptr)
     690             :                     modLayerCreationOptions.SetNameValue("FID",
     691          46 :                                                          pszSrcFIDColumn);
     692             :             }
     693             : 
     694           0 :             std::unique_ptr<OGRGeomFieldDefn> poFirstGeomFieldDefn;
     695          90 :             if (poSrcFeatureDefn->GetGeomFieldCount())
     696             :             {
     697          68 :                 poFirstGeomFieldDefn = std::make_unique<OGRGeomFieldDefn>(
     698          68 :                     *poSrcFeatureDefn->GetGeomFieldDefn(0));
     699          68 :                 if (abPartitionedGeomFields[0])
     700             :                 {
     701           6 :                     if (aeGeomTypes[0] == wkbNone)
     702           1 :                         poFirstGeomFieldDefn.reset();
     703             :                     else
     704          10 :                         whileUnsealing(poFirstGeomFieldDefn.get())
     705           5 :                             ->SetType(aeGeomTypes[0]);
     706             :                 }
     707             :             }
     708         180 :             auto poLayer = outputLayer->poDS->CreateLayer(
     709          90 :                 poSrcLayer->GetDescription(), poFirstGeomFieldDefn.get(),
     710          90 :                 modLayerCreationOptions.List());
     711          90 :             if (!poLayer)
     712             :             {
     713           1 :                 return false;
     714             :             }
     715          89 :             outputLayer->poLayer = poLayer;
     716          89 :             int iField = -1;
     717         432 :             for (const auto *poFieldDefn : poSrcFeatureDefn->GetFields())
     718             :             {
     719         344 :                 ++iField;
     720         344 :                 if (omitPartitionedFields && abPartitionedFields[iField])
     721          23 :                     continue;
     722         321 :                 if (poLayer->CreateField(poFieldDefn) != OGRERR_NONE)
     723             :                 {
     724           1 :                     alg->ReportError(CE_Failure, CPLE_AppDefined,
     725             :                                      "Cannot create field '%s'",
     726             :                                      poFieldDefn->GetNameRef());
     727           1 :                     return false;
     728             :                 }
     729             :             }
     730          88 :             int iGeomField = -1;
     731          68 :             for (const auto *poGeomFieldDefn :
     732         224 :                  poSrcFeatureDefn->GetGeomFields())
     733             :             {
     734          69 :                 ++iGeomField;
     735          69 :                 if (iGeomField > 0)
     736             :                 {
     737           3 :                     OGRGeomFieldDefn oClone(poGeomFieldDefn);
     738           3 :                     if (abPartitionedGeomFields[iGeomField])
     739             :                     {
     740           2 :                         if (aeGeomTypes[iGeomField] == wkbNone)
     741           0 :                             continue;
     742           4 :                         whileUnsealing(&oClone)->SetType(
     743           2 :                             aeGeomTypes[iGeomField]);
     744             :                     }
     745           3 :                     if (poLayer->CreateGeomField(&oClone) != OGRERR_NONE)
     746             :                     {
     747           1 :                         alg->ReportError(CE_Failure, CPLE_AppDefined,
     748             :                                          "Cannot create geometry field '%s'",
     749             :                                          poGeomFieldDefn->GetNameRef());
     750           1 :                         return false;
     751             :                     }
     752             :                 }
     753             :             }
     754             : 
     755          87 :             if (bUseTransactions)
     756             :             {
     757          70 :                 if (outputLayer->poDS->StartTransaction() != OGRERR_NONE)
     758           0 :                     return false;
     759             :             }
     760             :         }
     761             : 
     762          90 :         const auto nCounter = CPLGetErrorCounter();
     763          90 :         oCacheOutputLayer.insert(osKey, outputLayer);
     764             :         // In case insertion caused an eviction and old dataset
     765             :         // flushing caused an error
     766          90 :         if (CPLGetErrorCounter() != nCounter)
     767           0 :             return false;
     768             :     }
     769             : 
     770       10109 :     return true;
     771             : }
     772             : 
     773             : /************************************************************************/
     774             : /*               GDALVectorPartitionAlgorithm::RunStep()                */
     775             : /************************************************************************/
     776             : 
     777          46 : bool GDALVectorPartitionAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
     778             : {
     779          46 :     auto poSrcDS = m_inputDataset[0].GetDatasetRef();
     780          46 :     CPLAssert(poSrcDS);
     781             : 
     782          46 :     auto poOutDriver = poSrcDS->GetDriver();
     783             :     const char *pszExtensions =
     784          46 :         poOutDriver ? poOutDriver->GetMetadataItem(GDAL_DMD_EXTENSIONS)
     785          46 :                     : nullptr;
     786          46 :     if (m_format.empty())
     787             :     {
     788           1 :         if (!pszExtensions)
     789             :         {
     790           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     791             :                         "Cannot infer output format. Please specify "
     792             :                         "'output-format' argument");
     793           1 :             return false;
     794             :         }
     795             :     }
     796             :     else
     797             :     {
     798          45 :         poOutDriver = GetGDALDriverManager()->GetDriverByName(m_format.c_str());
     799          90 :         if (!(poOutDriver && (pszExtensions = poOutDriver->GetMetadataItem(
     800          45 :                                   GDAL_DMD_EXTENSIONS)) != nullptr))
     801             :         {
     802           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     803             :                         "Output driver has no known file extension");
     804           1 :             return false;
     805             :         }
     806             :     }
     807          44 :     CPLAssert(poOutDriver);
     808             : 
     809             :     const bool bFormatSupportsAppend =
     810          46 :         poOutDriver->GetMetadataItem(GDAL_DCAP_UPDATE) ||
     811           2 :         poOutDriver->GetMetadataItem(GDAL_DCAP_APPEND);
     812          44 :     if (m_appendLayer && !bFormatSupportsAppend)
     813             :     {
     814           1 :         ReportError(CE_Failure, CPLE_AppDefined,
     815             :                     "Driver '%s' does not support update",
     816           1 :                     poOutDriver->GetDescription());
     817           1 :         return false;
     818             :     }
     819             : 
     820          43 :     const bool bParquetOutput = EQUAL(poOutDriver->GetDescription(), "PARQUET");
     821          43 :     if (bParquetOutput && m_scheme == SCHEME_HIVE)
     822             :     {
     823             :         // Required for Parquet Hive partitioning
     824           4 :         m_omitPartitionedFields = true;
     825             :     }
     826             : 
     827          86 :     const CPLStringList aosExtensions(CSLTokenizeString(pszExtensions));
     828          43 :     const char *pszExtension = aosExtensions[0];
     829             : 
     830          86 :     const CPLStringList datasetCreationOptions(m_creationOptions);
     831          86 :     const CPLStringList layerCreationOptions(m_layerCreationOptions);
     832             : 
     833             :     // We don't have driver metadata for that (and that would be a bit
     834             :     // tricky because some formats are half-text/half-binary), so...
     835             :     const bool bOutputFormatIsBinary =
     836          39 :         bParquetOutput || EQUAL(poOutDriver->GetDescription(), "GPKG") ||
     837          83 :         EQUAL(poOutDriver->GetDescription(), "SQLite") ||
     838           1 :         EQUAL(poOutDriver->GetDescription(), "FlatGeoBuf");
     839             : 
     840             :     // Below values have been experimentally determined and are not based
     841             :     // on rocket science...
     842          43 :     int nSpatialIndexPerFeatureConstant = 0;
     843          43 :     int nSpatialIndexPerLog2FeatureCountConstant = 0;
     844          43 :     if (CPLTestBool(
     845             :             layerCreationOptions.FetchNameValueDef("SPATIAL_INDEX", "YES")))
     846             :     {
     847          42 :         if (EQUAL(poOutDriver->GetDescription(), "GPKG"))
     848             :         {
     849          36 :             nSpatialIndexPerFeatureConstant =
     850             :                 static_cast<int>(sizeof(double) * 4 + sizeof(uint32_t));
     851          36 :             nSpatialIndexPerLog2FeatureCountConstant = 1;
     852             :         }
     853           6 :         else if (EQUAL(poOutDriver->GetDescription(), "FlatGeoBuf"))
     854             :         {
     855           0 :             nSpatialIndexPerFeatureConstant = 1;
     856           0 :             nSpatialIndexPerLog2FeatureCountConstant =
     857             :                 static_cast<int>(sizeof(double) * 4 + sizeof(uint64_t));
     858             :         }
     859             :     }
     860             : 
     861             :     const bool bUseTransactions =
     862          43 :         (EQUAL(poOutDriver->GetDescription(), "GPKG") ||
     863          81 :          EQUAL(poOutDriver->GetDescription(), "SQLite")) &&
     864          38 :         !m_skipErrors;
     865             : 
     866             :     VSIStatBufL sStat;
     867          43 :     if (VSIStatL(m_output.c_str(), &sStat) == 0)
     868             :     {
     869          11 :         if (m_overwrite)
     870             :         {
     871           5 :             bool emptyDir = true;
     872           5 :             bool hasDirLevel1WithEqual = false;
     873             : 
     874             :             // Do a sanity check to verify that this looks like a directory
     875             :             // generated by partition
     876             : 
     877           5 :             if (m_scheme == SCHEME_HIVE)
     878             :             {
     879             :                 std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
     880           3 :                     VSIOpenDir(m_output.c_str(), -1, nullptr), VSICloseDir);
     881           3 :                 if (psDir)
     882             :                 {
     883             :                     while (const auto *psEntry =
     884           6 :                                VSIGetNextDirEntry(psDir.get()))
     885             :                     {
     886           5 :                         emptyDir = false;
     887           5 :                         if (VSI_ISDIR(psEntry->nMode))
     888             :                         {
     889           5 :                             std::string_view v(psEntry->pszName);
     890           5 :                             if (std::count_if(
     891         129 :                                     v.begin(), v.end(), [](char c)
     892         134 :                                     { return c == '/' || c == '\\'; }) == 1)
     893             :                             {
     894           2 :                                 const auto nPosDirSep = v.find_first_of("/\\");
     895           2 :                                 const auto nPosEqual = v.find('=', nPosDirSep);
     896           2 :                                 if (nPosEqual != std::string::npos)
     897             :                                 {
     898           2 :                                     hasDirLevel1WithEqual = true;
     899           2 :                                     break;
     900             :                                 }
     901             :                             }
     902             :                         }
     903           3 :                     }
     904             :                 }
     905             : 
     906           3 :                 if (!hasDirLevel1WithEqual && !emptyDir)
     907             :                 {
     908           1 :                     ReportError(
     909             :                         CE_Failure, CPLE_AppDefined,
     910             :                         "Rejecting removing '%s' as it does not look like "
     911             :                         "a directory generated by this utility. If you are "
     912             :                         "sure, remove it manually and re-run",
     913             :                         m_output.c_str());
     914           1 :                     return false;
     915             :                 }
     916             :             }
     917             :             else
     918             :             {
     919           2 :                 bool hasSubDir = false;
     920             :                 std::unique_ptr<VSIDIR, decltype(&VSICloseDir)> psDir(
     921           2 :                     VSIOpenDir(m_output.c_str(), 0, nullptr), VSICloseDir);
     922           2 :                 if (psDir)
     923             :                 {
     924             :                     while (const auto *psEntry =
     925           6 :                                VSIGetNextDirEntry(psDir.get()))
     926             :                     {
     927           5 :                         if (VSI_ISDIR(psEntry->nMode))
     928             :                         {
     929           1 :                             hasSubDir = true;
     930           1 :                             break;
     931             :                         }
     932           4 :                     }
     933             :                 }
     934             : 
     935           2 :                 if (hasSubDir)
     936             :                 {
     937           1 :                     ReportError(
     938             :                         CE_Failure, CPLE_AppDefined,
     939             :                         "Rejecting removing '%s' as it does not look like "
     940             :                         "a directory generated by this utility. If you are "
     941             :                         "sure, remove it manually and re-run",
     942             :                         m_output.c_str());
     943           1 :                     return false;
     944             :                 }
     945             :             }
     946             : 
     947           3 :             if (VSIRmdirRecursive(m_output.c_str()) != 0)
     948             :             {
     949           0 :                 ReportError(CE_Failure, CPLE_AppDefined, "Cannot remove '%s'",
     950             :                             m_output.c_str());
     951           0 :                 return false;
     952             :             }
     953             :         }
     954           6 :         else if (!m_appendLayer)
     955             :         {
     956           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     957             :                         "'%s' already exists. Specify --overwrite or --append",
     958             :                         m_output.c_str());
     959           1 :             return false;
     960             :         }
     961             :     }
     962          40 :     if (VSIStatL(m_output.c_str(), &sStat) != 0)
     963             :     {
     964          35 :         if (VSIMkdir(m_output.c_str(), DIRECTORY_CREATION_MODE) != 0)
     965             :         {
     966           1 :             ReportError(CE_Failure, CPLE_AppDefined,
     967             :                         "Cannot create directory '%s'", m_output.c_str());
     968           1 :             return false;
     969             :         }
     970             :     }
     971             : 
     972          91 :     for (OGRLayer *poSrcLayer : poSrcDS->GetLayers())
     973             :     {
     974             :         const std::string osLayerDir =
     975          63 :             m_scheme == SCHEME_HIVE
     976             :                 ? CPLFormFilenameSafe(
     977             :                       m_output.c_str(),
     978         177 :                       PercentEncode(poSrcLayer->GetDescription()).c_str(),
     979             :                       nullptr)
     980         120 :                 : m_output;
     981         120 :         if (m_scheme == SCHEME_HIVE &&
     982          57 :             VSIStatL(osLayerDir.c_str(), &sStat) != 0)
     983             :         {
     984          50 :             if (VSIMkdir(osLayerDir.c_str(), DIRECTORY_CREATION_MODE) != 0)
     985             :             {
     986           0 :                 ReportError(CE_Failure, CPLE_AppDefined,
     987             :                             "Cannot create directory '%s'", osLayerDir.c_str());
     988           0 :                 return false;
     989             :             }
     990             :         }
     991             : 
     992          63 :         const auto poSrcFeatureDefn = poSrcLayer->GetLayerDefn();
     993             : 
     994             :         struct Field
     995             :         {
     996             :             int nIdx{};
     997             :             bool bIsGeom = false;
     998             :             std::string encodedFieldName{};
     999             :             OGRFieldType eType{};
    1000             :         };
    1001             : 
    1002          63 :         std::vector<Field> asFields;
    1003         126 :         std::vector<bool> abPartitionedFields(poSrcFeatureDefn->GetFieldCount(),
    1004          63 :                                               false);
    1005             :         std::vector<bool> abPartitionedGeomFields(
    1006          63 :             poSrcFeatureDefn->GetGeomFieldCount(), false);
    1007         124 :         for (const std::string &fieldName : m_fields)
    1008             :         {
    1009          63 :             int nIdx = poSrcFeatureDefn->GetFieldIndex(fieldName.c_str());
    1010          63 :             if (nIdx < 0)
    1011             :             {
    1012           3 :                 if (EQUAL(fieldName.c_str(), "OGR_GEOMETRY") &&
    1013           0 :                     poSrcFeatureDefn->GetGeomFieldCount() > 0)
    1014             :                 {
    1015           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1016             :                              "'%s' is deprecated. Please use '%s' instead",
    1017             :                              "OGR_GEOMETRY",
    1018             :                              OGR_GEOMETRY_DEFAULT_NON_EMPTY_NAME);
    1019           0 :                     nIdx = 0;
    1020             :                 }
    1021           3 :                 else if (EQUAL(fieldName.c_str(),
    1022           4 :                                OGR_GEOMETRY_DEFAULT_NON_EMPTY_NAME) &&
    1023           1 :                          poSrcFeatureDefn->GetGeomFieldCount() > 0)
    1024             :                 {
    1025           1 :                     nIdx = 0;
    1026             :                 }
    1027             :                 else
    1028             :                     nIdx =
    1029           2 :                         poSrcFeatureDefn->GetGeomFieldIndex(fieldName.c_str());
    1030           3 :                 if (nIdx < 0)
    1031             :                 {
    1032           1 :                     ReportError(CE_Failure, CPLE_AppDefined,
    1033             :                                 "Cannot find field '%s' in layer '%s'",
    1034             :                                 fieldName.c_str(),
    1035           1 :                                 poSrcLayer->GetDescription());
    1036           2 :                     return false;
    1037             :                 }
    1038             :                 else
    1039             :                 {
    1040           2 :                     abPartitionedGeomFields[nIdx] = true;
    1041           4 :                     Field f;
    1042           2 :                     f.nIdx = nIdx;
    1043           2 :                     f.bIsGeom = true;
    1044           2 :                     if (fieldName.empty())
    1045             :                         f.encodedFieldName =
    1046           0 :                             OGR_GEOMETRY_DEFAULT_NON_EMPTY_NAME;
    1047             :                     else
    1048           2 :                         f.encodedFieldName = PercentEncode(fieldName);
    1049           2 :                     asFields.push_back(std::move(f));
    1050             :                 }
    1051             :             }
    1052             :             else
    1053             :             {
    1054             :                 const auto eType =
    1055          60 :                     poSrcFeatureDefn->GetFieldDefn(nIdx)->GetType();
    1056          60 :                 if (eType != OFTString && eType != OFTInteger &&
    1057             :                     eType != OFTInteger64)
    1058             :                 {
    1059           1 :                     ReportError(
    1060             :                         CE_Failure, CPLE_NotSupported,
    1061             :                         "Field '%s' not valid for partitioning. Only fields of "
    1062             :                         "type String, Integer or Integer64, or geometry fields,"
    1063             :                         " are accepted",
    1064             :                         fieldName.c_str());
    1065           1 :                     return false;
    1066             :                 }
    1067          59 :                 abPartitionedFields[nIdx] = true;
    1068         118 :                 Field f;
    1069          59 :                 f.nIdx = nIdx;
    1070          59 :                 f.bIsGeom = false;
    1071          59 :                 f.encodedFieldName = PercentEncode(fieldName);
    1072          59 :                 f.eType = eType;
    1073          59 :                 asFields.push_back(std::move(f));
    1074             :             }
    1075             :         }
    1076             : 
    1077          61 :         std::vector<OGRFieldType> aeSrcFieldTypes;
    1078         305 :         for (const auto *poFieldDefn : poSrcFeatureDefn->GetFields())
    1079             :         {
    1080         244 :             aeSrcFieldTypes.push_back(poFieldDefn->GetType());
    1081             :         }
    1082             : 
    1083             :         std::unique_ptr<OGRFeatureDefn> poFeatureDefnWithoutPartitionedFields(
    1084          61 :             poSrcFeatureDefn->Clone());
    1085          61 :         std::vector<int> anMapForSetFrom;
    1086          61 :         if (m_omitPartitionedFields)
    1087             :         {
    1088             :             // Sort fields by descending index (so we can delete them easily)
    1089          10 :             std::vector<Field> sortedFields(asFields);
    1090          10 :             std::sort(sortedFields.begin(), sortedFields.end(),
    1091           4 :                       [](const Field &a, const Field &b)
    1092           4 :                       { return a.nIdx > b.nIdx; });
    1093          24 :             for (const auto &field : sortedFields)
    1094             :             {
    1095          14 :                 if (!field.bIsGeom)
    1096          14 :                     poFeatureDefnWithoutPartitionedFields->DeleteFieldDefn(
    1097          14 :                         field.nIdx);
    1098             :             }
    1099             :             anMapForSetFrom =
    1100          20 :                 poFeatureDefnWithoutPartitionedFields->ComputeMapForSetFrom(
    1101          10 :                     poSrcFeatureDefn);
    1102             :         }
    1103             : 
    1104             :         lru11::Cache<std::string, std::shared_ptr<Layer>> oCacheOutputLayer(
    1105          61 :             m_maxCacheSize, 0);
    1106          61 :         std::shared_ptr<Layer> outputLayer = std::make_unique<Layer>();
    1107          61 :         outputLayer->bUseTransactions = bUseTransactions;
    1108             : 
    1109          61 :         GIntBig nTotalFeatures = 1;
    1110          61 :         GIntBig nFeatureIter = 0;
    1111          61 :         if (ctxt.m_pfnProgress)
    1112           5 :             nTotalFeatures = poSrcLayer->GetFeatureCount(true);
    1113             :         const double dfInvTotalFeatures =
    1114          61 :             1.0 / static_cast<double>(std::max<GIntBig>(1, nTotalFeatures));
    1115             : 
    1116          61 :         std::string osAttrQueryString;
    1117          61 :         if (const char *pszAttrQueryString = poSrcLayer->GetAttrQueryString())
    1118           0 :             osAttrQueryString = pszAttrQueryString;
    1119             : 
    1120          61 :         std::string osKeyTmp;
    1121          61 :         std::vector<OGRwkbGeometryType> aeGeomTypesTmp;
    1122             :         const auto BuildKey =
    1123       10118 :             [&osKeyTmp, &aeGeomTypesTmp](const std::vector<Field> &fields,
    1124             :                                          const OGRFeature *poFeature)
    1125       70847 :             -> std::pair<const std::string &,
    1126             :                          const std::vector<OGRwkbGeometryType> &>
    1127             :         {
    1128       10118 :             osKeyTmp.clear();
    1129       10118 :             aeGeomTypesTmp.resize(poFeature->GetDefnRef()->GetGeomFieldCount());
    1130       20236 :             for (const auto &field : fields)
    1131             :             {
    1132       10118 :                 if (!osKeyTmp.empty())
    1133           8 :                     osKeyTmp += '/';
    1134       10118 :                 osKeyTmp += field.encodedFieldName;
    1135       10118 :                 osKeyTmp += '=';
    1136       10118 :                 if (field.bIsGeom)
    1137             :                 {
    1138           9 :                     const auto poGeom = poFeature->GetGeomFieldRef(field.nIdx);
    1139           9 :                     if (poGeom)
    1140             :                     {
    1141           8 :                         aeGeomTypesTmp[field.nIdx] = poGeom->getGeometryType();
    1142           8 :                         osKeyTmp += poGeom->getGeometryName();
    1143           8 :                         if (poGeom->Is3D())
    1144           2 :                             osKeyTmp += 'Z';
    1145           8 :                         if (poGeom->IsMeasured())
    1146           2 :                             osKeyTmp += 'M';
    1147             :                     }
    1148             :                     else
    1149             :                     {
    1150           1 :                         aeGeomTypesTmp[field.nIdx] = wkbNone;
    1151           1 :                         osKeyTmp += NULL_MARKER;
    1152             :                     }
    1153             :                 }
    1154       10109 :                 else if (poFeature->IsFieldSetAndNotNull(field.nIdx))
    1155             :                 {
    1156       10081 :                     if (field.eType == OFTString)
    1157             :                     {
    1158       10068 :                         PercentEncode(
    1159             :                             osKeyTmp,
    1160       10068 :                             poFeature->GetFieldAsStringUnsafe(field.nIdx));
    1161             :                     }
    1162          13 :                     else if (field.eType == OFTInteger)
    1163             :                     {
    1164             :                         osKeyTmp += CPLSPrintf(
    1165             :                             "%d",
    1166          12 :                             poFeature->GetFieldAsIntegerUnsafe(field.nIdx));
    1167             :                     }
    1168             :                     else
    1169             :                     {
    1170             :                         osKeyTmp += CPLSPrintf(
    1171             :                             CPL_FRMT_GIB,
    1172           1 :                             poFeature->GetFieldAsInteger64Unsafe(field.nIdx));
    1173             :                     }
    1174             :                 }
    1175             :                 else
    1176             :                 {
    1177          28 :                     osKeyTmp += NULL_MARKER;
    1178             :                 }
    1179             :             }
    1180       10118 :             return {osKeyTmp, aeGeomTypesTmp};
    1181          61 :         };
    1182             : 
    1183          61 :         std::set<std::string> oSetKeys;
    1184          61 :         if (!bFormatSupportsAppend)
    1185             :         {
    1186           2 :             CPLDebug(
    1187             :                 "GDAL",
    1188             :                 "First pass to determine all distinct partitioned values...");
    1189             : 
    1190           2 :             if (asFields.size() == 1 && !asFields[0].bIsGeom)
    1191             :             {
    1192           2 :                 std::string osSQL = "SELECT DISTINCT \"";
    1193           2 :                 osSQL += CPLString(m_fields[0]).replaceAll('"', "\"\"");
    1194           2 :                 osSQL += "\" FROM \"";
    1195           2 :                 osSQL += CPLString(poSrcLayer->GetDescription())
    1196           2 :                              .replaceAll('"', "\"\"");
    1197           2 :                 osSQL += '"';
    1198           2 :                 if (!osAttrQueryString.empty())
    1199             :                 {
    1200           0 :                     osSQL += " WHERE ";
    1201           0 :                     osSQL += osAttrQueryString;
    1202             :                 }
    1203             :                 auto poSQLLayer =
    1204           2 :                     poSrcDS->ExecuteSQL(osSQL.c_str(), nullptr, nullptr);
    1205           2 :                 if (!poSQLLayer)
    1206           0 :                     return false;
    1207           8 :                 std::vector<Field> asSingleField{asFields[0]};
    1208           2 :                 asSingleField[0].nIdx = 0;
    1209           5 :                 for (auto &poFeature : *poSQLLayer)
    1210             :                 {
    1211           3 :                     const auto sPair = BuildKey(asFields, poFeature.get());
    1212           3 :                     const std::string &osKey = sPair.first;
    1213           3 :                     oSetKeys.insert(osKey);
    1214             : #ifdef DEBUG_VERBOSE
    1215             :                     CPLDebug("GDAL", "Found %s", osKey.c_str());
    1216             : #endif
    1217             :                 }
    1218           2 :                 poSrcDS->ReleaseResultSet(poSQLLayer);
    1219             : 
    1220           2 :                 if (!osAttrQueryString.empty())
    1221             :                 {
    1222           0 :                     poSrcLayer->SetAttributeFilter(osAttrQueryString.c_str());
    1223             :                 }
    1224             :             }
    1225             :             else
    1226             :             {
    1227           0 :                 for (auto &poFeature : *poSrcLayer)
    1228             :                 {
    1229           0 :                     const auto sPair = BuildKey(asFields, poFeature.get());
    1230           0 :                     const std::string &osKey = sPair.first;
    1231           0 :                     if (oSetKeys.insert(osKey).second)
    1232             :                     {
    1233             : #ifdef DEBUG_VERBOSE
    1234             :                         CPLDebug("GDAL", "Found %s", osKey.c_str());
    1235             : #endif
    1236             :                     }
    1237             :                 }
    1238             :             }
    1239           2 :             CPLDebug("GDAL",
    1240             :                      "End of first pass: %d unique partitioning keys found -> "
    1241             :                      "%d pass(es) needed",
    1242           2 :                      static_cast<int>(oSetKeys.size()),
    1243           2 :                      static_cast<int>((oSetKeys.size() + m_maxCacheSize - 1) /
    1244           2 :                                       m_maxCacheSize));
    1245             : 
    1246             :             // If we have less distinct values as the maximum cache size, we
    1247             :             // can do a single iteration.
    1248           2 :             if (oSetKeys.size() <= static_cast<size_t>(m_maxCacheSize))
    1249           2 :                 oSetKeys.clear();
    1250             :         }
    1251             : 
    1252          61 :         std::set<std::string> oSetOutputDatasets;
    1253          61 :         auto oSetKeysIter = oSetKeys.begin();
    1254             :         while (true)
    1255             :         {
    1256             :             // Determine which keys are allowed for the current pass
    1257          61 :             std::set<std::string> oSetKeysAllowedInThisPass;
    1258          61 :             if (!oSetKeys.empty())
    1259             :             {
    1260           0 :                 while (oSetKeysAllowedInThisPass.size() <
    1261           0 :                            static_cast<size_t>(m_maxCacheSize) &&
    1262           0 :                        oSetKeysIter != oSetKeys.end())
    1263             :                 {
    1264           0 :                     oSetKeysAllowedInThisPass.insert(*oSetKeysIter);
    1265           0 :                     ++oSetKeysIter;
    1266             :                 }
    1267           0 :                 if (oSetKeysAllowedInThisPass.empty())
    1268           0 :                     break;
    1269             :             }
    1270             : 
    1271       10168 :             for (auto &poFeature : *poSrcLayer)
    1272             :             {
    1273       10115 :                 const auto sPair = BuildKey(asFields, poFeature.get());
    1274       10115 :                 const std::string &osKey = sPair.first;
    1275       10115 :                 const auto &aeGeomTypes = sPair.second;
    1276             : 
    1277       10115 :                 if (!oSetKeysAllowedInThisPass.empty() &&
    1278           0 :                     !cpl::contains(oSetKeysAllowedInThisPass, osKey))
    1279             :                 {
    1280           1 :                     continue;
    1281             :                 }
    1282             : 
    1283       20230 :                 if (!GetCurrentOutputLayer(
    1284             :                         this, poSrcFeatureDefn, poSrcLayer, osKey, aeGeomTypes,
    1285       10115 :                         osLayerDir, m_scheme, m_pattern,
    1286       10115 :                         m_partDigitLeadingZeroes, m_partDigitCount,
    1287       10115 :                         m_featureLimit, m_maxFileSize, m_omitPartitionedFields,
    1288             :                         abPartitionedFields, abPartitionedGeomFields,
    1289             :                         pszExtension, poOutDriver, datasetCreationOptions,
    1290             :                         layerCreationOptions,
    1291       10115 :                         poFeatureDefnWithoutPartitionedFields.get(),
    1292       10115 :                         poFeature->GetGeometryRef()
    1293             :                             ? nSpatialIndexPerFeatureConstant
    1294             :                             : 0,
    1295             :                         nSpatialIndexPerLog2FeatureCountConstant,
    1296             :                         bUseTransactions, oCacheOutputLayer, outputLayer))
    1297             :                 {
    1298           8 :                     return false;
    1299             :                 }
    1300             : 
    1301       10109 :                 if (bParquetOutput)
    1302             :                 {
    1303             :                     oSetOutputDatasets.insert(
    1304          14 :                         outputLayer->poDS->GetDescription());
    1305             :                 }
    1306             : 
    1307       10109 :                 if (m_appendLayer)
    1308           8 :                     poFeature->SetFID(OGRNullFID);
    1309             : 
    1310             :                 OGRErr eErr;
    1311       20200 :                 if (m_omitPartitionedFields ||
    1312           0 :                     std::find(aeGeomTypes.begin(), aeGeomTypes.end(),
    1313       20200 :                               wkbNone) != aeGeomTypes.end())
    1314             :                 {
    1315          19 :                     OGRFeature oFeat(outputLayer->poLayer->GetLayerDefn());
    1316          19 :                     oFeat.SetFrom(poFeature.get(), anMapForSetFrom.data());
    1317          19 :                     oFeat.SetFID(poFeature->GetFID());
    1318          19 :                     eErr = outputLayer->poLayer->CreateFeature(&oFeat);
    1319             :                 }
    1320             :                 else
    1321             :                 {
    1322       20180 :                     poFeature->SetFDefnUnsafe(
    1323       10090 :                         outputLayer->poLayer->GetLayerDefn());
    1324       10090 :                     eErr = outputLayer->poLayer->CreateFeature(poFeature.get());
    1325             :                 }
    1326       10109 :                 if (eErr != OGRERR_NONE)
    1327             :                 {
    1328           2 :                     ReportError(m_skipErrors ? CE_Warning : CE_Failure,
    1329             :                                 CPLE_AppDefined,
    1330             :                                 "Cannot insert feature " CPL_FRMT_GIB,
    1331             :                                 poFeature->GetFID());
    1332           2 :                     if (m_skipErrors)
    1333           1 :                         continue;
    1334           1 :                     return false;
    1335             :                 }
    1336       10107 :                 ++outputLayer->nFeatureCount;
    1337             : 
    1338       20194 :                 if (bUseTransactions &&
    1339       10087 :                     (outputLayer->nFeatureCount % m_transactionSize) == 0)
    1340             :                 {
    1341           8 :                     if (outputLayer->poDS->CommitTransaction() != OGRERR_NONE ||
    1342           4 :                         outputLayer->poDS->StartTransaction() != OGRERR_NONE)
    1343             :                     {
    1344           0 :                         return false;
    1345             :                     }
    1346             :                 }
    1347             : 
    1348             :                 // Compute a rough estimate of the space taken by the feature
    1349       10107 :                 if (m_maxFileSize > 0)
    1350             :                 {
    1351       10000 :                     outputLayer->nFileSize += GetEstimatedFeatureSize(
    1352       10000 :                         poFeature.get(), abPartitionedFields,
    1353       10000 :                         m_omitPartitionedFields, aeSrcFieldTypes,
    1354             :                         bOutputFormatIsBinary);
    1355             :                 }
    1356             : 
    1357       10107 :                 ++nFeatureIter;
    1358       10116 :                 if (ctxt.m_pfnProgress &&
    1359           9 :                     !ctxt.m_pfnProgress(
    1360       10107 :                         std::min(1.0, static_cast<double>(nFeatureIter) *
    1361           9 :                                           dfInvTotalFeatures),
    1362             :                         "", ctxt.m_pProgressData))
    1363             :                 {
    1364           1 :                     ReportError(CE_Failure, CPLE_UserInterrupt,
    1365             :                                 "Interrupted by user");
    1366           1 :                     return false;
    1367             :                 }
    1368             :             }
    1369             : 
    1370          53 :             if (oSetKeysIter == oSetKeys.end())
    1371          53 :                 break;
    1372           0 :         }
    1373             : 
    1374          53 :         const auto nCounter = CPLGetErrorCounter();
    1375          53 :         outputLayer.reset();
    1376          53 :         oCacheOutputLayer.clear();
    1377          53 :         if (CPLGetErrorCounter() != nCounter)
    1378           1 :             return false;
    1379             : 
    1380             :         // For Parquet output, create special "_metadata" file that contains
    1381             :         // the schema and references the individual files
    1382          52 :         if (bParquetOutput && !oSetOutputDatasets.empty())
    1383             :         {
    1384             :             auto poAlg =
    1385           7 :                 GDALGlobalAlgorithmRegistry::GetSingleton().Instantiate(
    1386          14 :                     "driver", "parquet", "create-metadata-file");
    1387           7 :             if (poAlg)
    1388             :             {
    1389           7 :                 auto inputArg = poAlg->GetArg(GDAL_ARG_NAME_INPUT);
    1390           7 :                 auto outputArg = poAlg->GetArg(GDAL_ARG_NAME_OUTPUT);
    1391           7 :                 if (inputArg && inputArg->GetType() == GAAT_DATASET_LIST &&
    1392          14 :                     outputArg && outputArg->GetType() == GAAT_DATASET)
    1393             :                 {
    1394           7 :                     std::vector<std::string> asInputFilenames;
    1395           7 :                     asInputFilenames.insert(asInputFilenames.end(),
    1396             :                                             oSetOutputDatasets.begin(),
    1397          14 :                                             oSetOutputDatasets.end());
    1398           7 :                     inputArg->Set(asInputFilenames);
    1399           7 :                     outputArg->Set(CPLFormFilenameSafe(osLayerDir.c_str(),
    1400             :                                                        "_metadata", nullptr));
    1401           7 :                     if (!poAlg->Run())
    1402           0 :                         return false;
    1403             :                 }
    1404             :             }
    1405             :         }
    1406             :     }
    1407             : 
    1408          28 :     return true;
    1409             : }
    1410             : 
    1411             : /************************************************************************/
    1412             : /*               GDALVectorPartitionAlgorithm::RunImpl()                */
    1413             : /************************************************************************/
    1414             : 
    1415          45 : bool GDALVectorPartitionAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
    1416             :                                            void *pProgressData)
    1417             : {
    1418          45 :     GDALPipelineStepRunContext stepCtxt;
    1419          45 :     stepCtxt.m_pfnProgress = pfnProgress;
    1420          45 :     stepCtxt.m_pProgressData = pProgressData;
    1421          90 :     return RunStep(stepCtxt);
    1422             : }
    1423             : 
    1424             : GDALVectorPartitionAlgorithmStandalone::
    1425             :     ~GDALVectorPartitionAlgorithmStandalone() = default;
    1426             : //! @endcond

Generated by: LCOV version 1.14