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