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
|