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

Generated by: LCOV version 1.14