Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: gdal "raster/vector pipeline" subcommand
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2024-2025, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_conv.h"
14 : #include "cpl_enumerate.h"
15 : #include "cpl_error_internal.h"
16 : #include "cpl_json.h"
17 :
18 : #include "gdalalg_abstract_pipeline.h"
19 : #include "gdalalg_materialize.h"
20 : #include "gdalalg_raster_read.h"
21 : #include "gdalalg_raster_write.h"
22 : #include "gdalalg_vector_read.h"
23 : #include "gdalalg_tee.h"
24 :
25 : #include "vrtdataset.h"
26 :
27 : #include <algorithm>
28 : #include <cassert>
29 :
30 : //! @cond Doxygen_Suppress
31 :
32 : /* clang-format off */
33 : constexpr const char *const apszReadParametersPrefixOmitted[] = {
34 : GDAL_ARG_NAME_INPUT,
35 : GDAL_ARG_NAME_INPUT_FORMAT,
36 : GDAL_ARG_NAME_OPEN_OPTION,
37 : GDAL_ARG_NAME_INPUT_LAYER};
38 :
39 : constexpr const char *const apszWriteParametersPrefixOmitted[] = {
40 : GDAL_ARG_NAME_OUTPUT,
41 : GDAL_ARG_NAME_OUTPUT_FORMAT,
42 : GDAL_ARG_NAME_CREATION_OPTION,
43 : GDAL_ARG_NAME_OUTPUT_LAYER,
44 : GDAL_ARG_NAME_LAYER_CREATION_OPTION,
45 : GDAL_ARG_NAME_UPDATE,
46 : GDAL_ARG_NAME_OVERWRITE,
47 : GDAL_ARG_NAME_APPEND,
48 : GDAL_ARG_NAME_OVERWRITE_LAYER};
49 :
50 : /* clang-format on */
51 :
52 : /************************************************************************/
53 : /* IsReadSpecificArgument() */
54 : /************************************************************************/
55 :
56 : /* static */
57 36 : bool GDALAbstractPipelineAlgorithm::IsReadSpecificArgument(
58 : const char *pszArgName)
59 : {
60 36 : return std::find_if(std::begin(apszReadParametersPrefixOmitted),
61 : std::end(apszReadParametersPrefixOmitted),
62 118 : [pszArgName](const char *pszStr)
63 118 : { return strcmp(pszStr, pszArgName) == 0; }) !=
64 36 : std::end(apszReadParametersPrefixOmitted);
65 : }
66 :
67 : /************************************************************************/
68 : /* IsWriteSpecificArgument() */
69 : /************************************************************************/
70 :
71 : /* static */
72 53 : bool GDALAbstractPipelineAlgorithm::IsWriteSpecificArgument(
73 : const char *pszArgName)
74 : {
75 53 : return std::find_if(std::begin(apszWriteParametersPrefixOmitted),
76 : std::end(apszWriteParametersPrefixOmitted),
77 241 : [pszArgName](const char *pszStr)
78 241 : { return strcmp(pszStr, pszArgName) == 0; }) !=
79 53 : std::end(apszWriteParametersPrefixOmitted);
80 : }
81 :
82 : /************************************************************************/
83 : /* GDALAbstractPipelineAlgorithm::CheckFirstAndLastStep() */
84 : /************************************************************************/
85 :
86 447 : bool GDALAbstractPipelineAlgorithm::CheckFirstAndLastStep(
87 : const std::vector<GDALPipelineStepAlgorithm *> &steps,
88 : bool forAutoComplete) const
89 : {
90 447 : if (m_bExpectReadStep && !steps.front()->CanBeFirstStep())
91 : {
92 6 : std::set<CPLString> setFirstStepNames;
93 168 : for (const auto &stepName : GetStepRegistry().GetNames())
94 : {
95 330 : auto alg = GetStepAlg(stepName);
96 184 : if (alg && alg->CanBeFirstStep() &&
97 19 : stepName != GDALRasterReadAlgorithm::NAME)
98 : {
99 34 : setFirstStepNames.insert(CPLString(stepName)
100 34 : .replaceAll(RASTER_SUFFIX, "")
101 17 : .replaceAll(VECTOR_SUFFIX, ""));
102 : }
103 : }
104 15 : std::vector<std::string> firstStepNames{GDALRasterReadAlgorithm::NAME};
105 18 : for (const std::string &s : setFirstStepNames)
106 15 : firstStepNames.push_back(s);
107 :
108 3 : std::string msg = "First step should be ";
109 21 : for (size_t i = 0; i < firstStepNames.size(); ++i)
110 : {
111 18 : if (i == firstStepNames.size() - 1)
112 3 : msg += " or ";
113 15 : else if (i > 0)
114 12 : msg += ", ";
115 18 : msg += '\'';
116 18 : msg += firstStepNames[i];
117 18 : msg += '\'';
118 : }
119 :
120 3 : ReportError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
121 3 : return false;
122 : }
123 :
124 444 : if (!m_bExpectReadStep)
125 : {
126 20 : if (steps.front()->CanBeFirstStep())
127 : {
128 1 : ReportError(CE_Failure, CPLE_AppDefined,
129 : "No read-like step like '%s' is allowed",
130 1 : steps.front()->GetName().c_str());
131 1 : return false;
132 : }
133 : }
134 :
135 443 : if (forAutoComplete)
136 26 : return true;
137 :
138 417 : if (m_eLastStepAsWrite == StepConstraint::CAN_NOT_BE)
139 : {
140 23 : if (steps.back()->CanBeLastStep() && !steps.back()->CanBeMiddleStep())
141 : {
142 2 : ReportError(CE_Failure, CPLE_AppDefined,
143 : "Last step in %s pipeline must not be a "
144 : "write-like step.",
145 2 : m_bInnerPipeline ? "an inner" : "a");
146 2 : return false;
147 : }
148 : }
149 :
150 633 : for (size_t i = 1; i < steps.size() - 1; ++i)
151 : {
152 219 : if (!steps[i]->CanBeMiddleStep())
153 : {
154 7 : if (steps[i]->CanBeFirstStep() && m_bExpectReadStep)
155 : {
156 3 : ReportError(CE_Failure, CPLE_AppDefined,
157 : "Only first step can be '%s'",
158 3 : steps[i]->GetName().c_str());
159 : }
160 8 : else if (steps[i]->CanBeLastStep() &&
161 4 : m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE)
162 : {
163 3 : ReportError(CE_Failure, CPLE_AppDefined,
164 : "Only last step can be '%s'",
165 3 : steps[i]->GetName().c_str());
166 : }
167 : else
168 : {
169 1 : ReportError(CE_Failure, CPLE_AppDefined,
170 : "'%s' is not allowed as an intermediate step",
171 1 : steps[i]->GetName().c_str());
172 1 : return false;
173 : }
174 : }
175 : }
176 :
177 417 : if (steps.size() >= 2 && steps.back()->CanBeFirstStep() &&
178 3 : !steps.back()->CanBeLastStep())
179 : {
180 2 : ReportError(CE_Failure, CPLE_AppDefined,
181 : "'%s' is only allowed as a first step",
182 2 : steps.back()->GetName().c_str());
183 2 : return false;
184 : }
185 :
186 440 : if (m_eLastStepAsWrite == StepConstraint::MUST_BE &&
187 28 : !steps.back()->CanBeLastStep())
188 : {
189 2 : std::set<CPLString> setLastStepNames;
190 87 : for (const auto &stepName : GetStepRegistry().GetNames())
191 : {
192 172 : auto alg = GetStepAlg(stepName);
193 86 : if (alg && alg->CanBeLastStep())
194 : {
195 : const CPLString nameWithoutSuffix =
196 13 : CPLString(stepName)
197 26 : .replaceAll(RASTER_SUFFIX, "")
198 39 : .replaceAll(VECTOR_SUFFIX, "");
199 13 : if (nameWithoutSuffix != GDALRasterWriteAlgorithm::NAME)
200 : {
201 11 : setLastStepNames.insert(nameWithoutSuffix);
202 : }
203 : }
204 : }
205 5 : std::vector<std::string> lastStepNames{GDALRasterWriteAlgorithm::NAME};
206 10 : for (const std::string &s : setLastStepNames)
207 9 : lastStepNames.push_back(s);
208 :
209 1 : std::string msg = "Last step should be ";
210 11 : for (size_t i = 0; i < lastStepNames.size(); ++i)
211 : {
212 10 : if (i == lastStepNames.size() - 1)
213 1 : msg += " or ";
214 9 : else if (i > 0)
215 8 : msg += ", ";
216 10 : msg += '\'';
217 10 : msg += lastStepNames[i];
218 10 : msg += '\'';
219 : }
220 :
221 1 : ReportError(CE_Failure, CPLE_AppDefined, "%s", msg.c_str());
222 1 : return false;
223 : }
224 :
225 411 : return true;
226 : }
227 :
228 : /************************************************************************/
229 : /* GDALAbstractPipelineAlgorithm::GetStepAlg() */
230 : /************************************************************************/
231 :
232 : std::unique_ptr<GDALPipelineStepAlgorithm>
233 3380 : GDALAbstractPipelineAlgorithm::GetStepAlg(const std::string &name) const
234 : {
235 6760 : auto alg = GetStepRegistry().Instantiate(name);
236 : return std::unique_ptr<GDALPipelineStepAlgorithm>(
237 6760 : cpl::down_cast<GDALPipelineStepAlgorithm *>(alg.release()));
238 : }
239 :
240 : /************************************************************************/
241 : /* GetDatasetType() */
242 : /************************************************************************/
243 :
244 : /** Return GDAL_OF_RASTER, GDAL_OF_VECTOR or 0 */
245 747 : static int GetDatasetType(GDALDataset *poDS)
246 : {
247 747 : if (poDS->GetLayerCount() > 0 && poDS->GetRasterCount() == 0)
248 350 : return GDAL_OF_VECTOR;
249 :
250 794 : if (poDS->GetLayerCount() == 0 &&
251 397 : (poDS->GetRasterCount() > 0 ||
252 15 : poDS->GetMetadata(GDAL_MDD_SUBDATASETS) != nullptr))
253 : {
254 382 : return GDAL_OF_RASTER;
255 : }
256 :
257 15 : return 0;
258 : }
259 :
260 : /************************************************************************/
261 : /* GetInputDatasetType() */
262 : /************************************************************************/
263 :
264 : /** Return GDAL_OF_RASTER, GDAL_OF_VECTOR or 0 */
265 : /* static */
266 138 : int GDALAbstractPipelineAlgorithm::GetInputDatasetType(
267 : const GDALPipelineStepAlgorithm *alg)
268 : {
269 138 : int ret = 0;
270 138 : const auto stepInputArg = alg->GetArg(GDAL_ARG_NAME_INPUT);
271 158 : if (stepInputArg && stepInputArg->IsExplicitlySet() &&
272 20 : (stepInputArg->GetType() == GAAT_DATASET ||
273 10 : stepInputArg->GetType() == GAAT_DATASET_LIST))
274 : {
275 20 : std::string inputDatasetName;
276 10 : if (stepInputArg->GetType() == GAAT_DATASET)
277 : {
278 : inputDatasetName =
279 0 : stepInputArg->Get<GDALArgDatasetValue>().GetName();
280 : }
281 : else
282 : {
283 10 : auto &val = stepInputArg->Get<std::vector<GDALArgDatasetValue>>();
284 10 : if (!val.empty())
285 : {
286 10 : inputDatasetName = val[0].GetName();
287 : }
288 : }
289 :
290 10 : if (!inputDatasetName.empty())
291 : {
292 10 : std::unique_ptr<GDALDataset> datasetHolder;
293 : GDALDataset *poDS;
294 : const auto oIter =
295 10 : alg->m_oMapDatasetNameToDataset.find(inputDatasetName);
296 10 : if (oIter != alg->m_oMapDatasetNameToDataset.end())
297 4 : poDS = oIter->second;
298 : else
299 : {
300 12 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
301 6 : datasetHolder.reset(
302 : GDALDataset::Open(inputDatasetName.c_str()));
303 6 : poDS = datasetHolder.get();
304 : }
305 10 : if (poDS)
306 : {
307 10 : ret = GetDatasetType(poDS);
308 : }
309 : }
310 : }
311 138 : return ret;
312 : }
313 :
314 : /************************************************************************/
315 : /* GDALAbstractPipelineAlgorithm::CopyStepAlgorithmFromAnother() */
316 : /************************************************************************/
317 :
318 : /** Copy arguments and other parameters from \a src to \a dst, typically
319 : * when turning a raster algorithm to the vector one of the same name, or
320 : * vice-versa.
321 : */
322 33 : bool GDALAbstractPipelineAlgorithm::CopyStepAlgorithmFromAnother(
323 : GDALPipelineStepAlgorithm *dst, const GDALPipelineStepAlgorithm *src,
324 : bool maybeWriteStep) const
325 : {
326 33 : if (src->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
327 : {
328 : const auto poSrcTeeAlg =
329 7 : dynamic_cast<const GDALTeeStepAlgorithmAbstract *>(src);
330 7 : auto poDstTeeAlg = dynamic_cast<GDALTeeStepAlgorithmAbstract *>(dst);
331 7 : CPLAssert(poSrcTeeAlg);
332 7 : CPLAssert(poDstTeeAlg);
333 7 : poDstTeeAlg->CopyFilenameBindingsFrom(poSrcTeeAlg);
334 : }
335 :
336 33 : if (maybeWriteStep)
337 : {
338 : // Propagate output parameters set at the pipeline level to the
339 : // "write" step
340 393 : for (auto &arg : dst->GetArgs())
341 : {
342 370 : if (!arg->IsHidden())
343 : {
344 322 : const auto pipelineArg = GetArg(arg->GetName());
345 326 : if (pipelineArg && pipelineArg->IsExplicitlySet() &&
346 4 : pipelineArg->GetType() == arg->GetType())
347 : {
348 4 : arg->SetSkipIfAlreadySet(true);
349 4 : [[maybe_unused]] bool ret = arg->SetFrom(*pipelineArg);
350 4 : CPLAssert(ret);
351 : }
352 : }
353 : }
354 : }
355 :
356 : // Propagate parameters set on the old algorithm to the new one
357 410 : for (const auto &srcArg : src->GetArgs())
358 : {
359 378 : if (srcArg->IsExplicitlySet())
360 : {
361 40 : auto dstArg = dst->GetArg(srcArg->GetName());
362 40 : if (!dstArg)
363 : {
364 1 : dst->ReportError(CE_Failure, CPLE_IllegalArg,
365 : "Option '--%s' is unknown",
366 1 : srcArg->GetName().c_str());
367 1 : return false;
368 : }
369 : else
370 : {
371 39 : dstArg->SetSkipIfAlreadySet(true);
372 39 : if (!dstArg->SetFrom(*srcArg))
373 0 : return false;
374 : }
375 : }
376 : }
377 :
378 : dst->m_oMapDatasetNameToDataset =
379 32 : std::move(src->m_oMapDatasetNameToDataset);
380 64 : dst->SetCallPath({dst->GetName()});
381 32 : dst->SetReferencePathForRelativePaths(GetReferencePathForRelativePaths());
382 32 : if (IsCalledFromCommandLine())
383 2 : dst->SetCalledFromCommandLine();
384 :
385 32 : return true;
386 : }
387 :
388 : /************************************************************************/
389 : /* GDALAbstractPipelineAlgorithm::ParseCommandLineArguments() */
390 : /************************************************************************/
391 :
392 464 : bool GDALAbstractPipelineAlgorithm::ParseCommandLineArguments(
393 : const std::vector<std::string> &argsIn)
394 : {
395 464 : return ParseCommandLineArguments(argsIn, /*forAutoComplete=*/false,
396 464 : /*pCurArgsForAutocomplete=*/nullptr);
397 : }
398 :
399 : /** Parse arguments of a pipeline.
400 : *
401 : * @param argsIn Pipeline arguments
402 : * @param forAutoComplete true if this method is called from GetAutoComplete()
403 : * @param[out] pCurArgsForAutocomplete Pointer to a vector of string, or null.
404 : * If provided, it will contain the arguments
405 : * of the active pipeline. Useful for
406 : * completion in nested pipelines.
407 : */
408 544 : bool GDALAbstractPipelineAlgorithm::ParseCommandLineArguments(
409 : const std::vector<std::string> &argsIn, bool forAutoComplete,
410 : std::vector<std::string> *pCurArgsForAutocomplete)
411 : {
412 1088 : std::vector<std::string> args = argsIn;
413 544 : if (pCurArgsForAutocomplete)
414 55 : *pCurArgsForAutocomplete = args;
415 :
416 544 : if (!m_bInnerPipeline && IsCalledFromCommandLine())
417 : {
418 122 : m_eLastStepAsWrite = StepConstraint::MUST_BE;
419 : }
420 :
421 610 : if (args.size() == 1 && (args[0] == "-h" || args[0] == "--help" ||
422 66 : args[0] == "help" || args[0] == "--json-usage"))
423 : {
424 5 : return GDALAlgorithm::ParseCommandLineArguments(args);
425 : }
426 539 : else if (args.size() == 1 && STARTS_WITH(args[0].c_str(), "--help-doc="))
427 : {
428 12 : m_helpDocCategory = args[0].substr(strlen("--help-doc="));
429 24 : return GDALAlgorithm::ParseCommandLineArguments({"--help-doc"});
430 : }
431 :
432 527 : bool foundStepMarker = false;
433 :
434 3912 : for (size_t i = 0; i < args.size(); ++i)
435 : {
436 3395 : const auto &arg = args[i];
437 3395 : if (arg == "--pipeline")
438 : {
439 10 : if (i + 1 < args.size() &&
440 10 : CPLString(args[i + 1]).ifind(".json") != std::string::npos)
441 2 : break;
442 3 : return GDALAlgorithm::ParseCommandLineArguments(args);
443 : }
444 :
445 3390 : else if (cpl::starts_with(arg, "--pipeline="))
446 : {
447 2 : if (CPLString(arg).ifind(".json") != std::string::npos)
448 1 : break;
449 1 : return GDALAlgorithm::ParseCommandLineArguments(args);
450 : }
451 :
452 : // gdal pipeline [--quiet] "read poly.gpkg ..."
453 3388 : if (arg.find("read ") == 0)
454 3 : return GDALAlgorithm::ParseCommandLineArguments(args);
455 :
456 3385 : if (arg == "!")
457 627 : foundStepMarker = true;
458 : }
459 :
460 520 : bool runExistingPipeline = false;
461 520 : if (!foundStepMarker && !m_executionForStreamOutput)
462 : {
463 103 : std::string osCommandLine;
464 288 : for (const auto &arg : args)
465 : {
466 212 : if (((!arg.empty() && arg[0] != '-') ||
467 425 : cpl::starts_with(arg, "--pipeline=")) &&
468 405 : CPLString(arg).ifind(".json") != std::string::npos)
469 : {
470 : bool ret;
471 27 : if (m_pipeline == arg)
472 2 : ret = true;
473 : else
474 : {
475 : const std::string filename =
476 25 : cpl::starts_with(arg, "--pipeline=")
477 : ? arg.substr(strlen("--pipeline="))
478 50 : : arg;
479 25 : if (forAutoComplete)
480 : {
481 6 : SetParseForAutoCompletion();
482 : }
483 25 : ret = GDALAlgorithm::ParseCommandLineArguments(args) ||
484 : forAutoComplete;
485 25 : if (ret)
486 : {
487 22 : ret = m_pipeline == filename;
488 : }
489 : }
490 27 : if (ret)
491 : {
492 24 : CPLJSONDocument oDoc;
493 24 : ret = oDoc.Load(m_pipeline);
494 24 : if (ret)
495 : {
496 : osCommandLine =
497 23 : oDoc.GetRoot().GetString("command_line");
498 23 : if (osCommandLine.empty())
499 : {
500 1 : ReportError(CE_Failure, CPLE_AppDefined,
501 : "command_line missing in %s",
502 : m_pipeline.c_str());
503 1 : return false;
504 : }
505 :
506 66 : for (const char *prefix :
507 : {"gdal pipeline ", "gdal raster pipeline ",
508 88 : "gdal vector pipeline "})
509 : {
510 66 : if (cpl::starts_with(osCommandLine, prefix))
511 : osCommandLine =
512 22 : osCommandLine.substr(strlen(prefix));
513 : }
514 :
515 22 : if (oDoc.GetRoot().GetBool(
516 : "relative_paths_relative_to_this_file", true))
517 : {
518 0 : SetReferencePathForRelativePaths(
519 0 : CPLGetPathSafe(m_pipeline.c_str()).c_str());
520 : }
521 :
522 22 : runExistingPipeline = true;
523 : }
524 : }
525 26 : if (ret)
526 22 : break;
527 : else
528 4 : return false;
529 : }
530 : }
531 98 : if (runExistingPipeline)
532 : {
533 : const CPLStringList aosArgs(
534 22 : CSLTokenizeString(osCommandLine.c_str()));
535 :
536 22 : args = aosArgs;
537 : }
538 : }
539 :
540 515 : if (!m_steps.empty())
541 : {
542 3 : ReportError(CE_Failure, CPLE_AppDefined,
543 : "ParseCommandLineArguments() can only be called once per "
544 : "instance.");
545 3 : return false;
546 : }
547 :
548 : const bool bIsGenericPipeline =
549 512 : (GetInputType() == (GDAL_OF_RASTER | GDAL_OF_VECTOR));
550 :
551 : struct Step
552 : {
553 : std::unique_ptr<GDALPipelineStepAlgorithm> alg{};
554 : std::vector<std::string> args{};
555 : bool alreadyChangedType = false;
556 : bool isSubAlgorithm = false;
557 : };
558 :
559 512 : int nDatasetType = GetInputType();
560 : const auto SetCurStepAlg =
561 1118 : [this, bIsGenericPipeline, &nDatasetType](
562 4605 : Step &curStep, const std::string &algName, bool firstStep)
563 : {
564 1118 : if (bIsGenericPipeline)
565 : {
566 488 : if (algName == GDALRasterReadAlgorithm::NAME)
567 : {
568 199 : curStep.alg = std::make_unique<GDALRasterReadAlgorithm>(true);
569 : }
570 : else
571 : {
572 289 : if (nDatasetType == GDAL_OF_RASTER)
573 53 : curStep.alg = GetStepAlg(algName + RASTER_SUFFIX);
574 236 : else if (nDatasetType == GDAL_OF_VECTOR)
575 24 : curStep.alg = GetStepAlg(algName + VECTOR_SUFFIX);
576 289 : if (!curStep.alg)
577 223 : curStep.alg = GetStepAlg(algName);
578 289 : if (!curStep.alg)
579 133 : curStep.alg = GetStepAlg(algName + RASTER_SUFFIX);
580 289 : if (curStep.alg)
581 284 : nDatasetType = curStep.alg->GetOutputType();
582 : }
583 : }
584 : else
585 : {
586 630 : curStep.alg = GetStepAlg(algName);
587 : }
588 1118 : if (!curStep.alg)
589 : {
590 14 : ReportError(CE_Failure, CPLE_AppDefined, "unknown step name: %s",
591 : algName.c_str());
592 14 : return false;
593 : }
594 : // We don't want to accept '_PIPE_' dataset placeholder for the first
595 : // step of a pipeline.
596 1104 : curStep.alg->m_inputDatasetCanBeOmitted =
597 1104 : !firstStep || !m_bExpectReadStep;
598 2208 : curStep.alg->SetCallPath({algName});
599 1104 : curStep.alg->SetReferencePathForRelativePaths(
600 : GetReferencePathForRelativePaths());
601 1104 : return true;
602 512 : };
603 :
604 1024 : std::vector<Step> steps;
605 512 : steps.resize(1);
606 :
607 512 : int nNestLevel = 0;
608 1024 : std::vector<std::string> nestedPipelineArgs;
609 :
610 3890 : for (const auto &argIn : args)
611 : {
612 3412 : std::string arg(argIn);
613 :
614 : // If outputting to stdout, automatically turn off progress bar
615 3412 : if (arg == "/vsistdout/")
616 : {
617 2 : auto quietArg = GetArg(GDAL_ARG_NAME_QUIET);
618 2 : if (quietArg && quietArg->GetType() == GAAT_BOOLEAN)
619 2 : quietArg->Set(true);
620 : }
621 :
622 3412 : auto &curStep = steps.back();
623 :
624 3412 : if (nNestLevel > 0)
625 : {
626 211 : if (arg == CLOSE_NESTED_PIPELINE)
627 : {
628 53 : if ((--nNestLevel) == 0)
629 : {
630 104 : arg = BuildNestedPipeline(curStep.alg.get(),
631 : nestedPipelineArgs,
632 52 : forAutoComplete, nullptr);
633 52 : if (arg.empty())
634 : {
635 8 : return false;
636 : }
637 44 : if (pCurArgsForAutocomplete)
638 1 : *pCurArgsForAutocomplete = args;
639 : }
640 : else
641 : {
642 1 : nestedPipelineArgs.push_back(std::move(arg));
643 1 : continue;
644 : }
645 : }
646 : else
647 : {
648 158 : if (arg == OPEN_NESTED_PIPELINE)
649 : {
650 3 : if (++nNestLevel == MAX_NESTING_LEVEL)
651 : {
652 1 : ReportError(CE_Failure, CPLE_AppDefined,
653 : "Too many nested pipelines");
654 1 : return false;
655 : }
656 : }
657 157 : nestedPipelineArgs.push_back(std::move(arg));
658 157 : continue;
659 : }
660 : }
661 :
662 3245 : if (arg == "--progress")
663 : {
664 5 : m_progressBarRequested = true;
665 5 : continue;
666 : }
667 3240 : if (arg == "-q" || arg == "--quiet")
668 : {
669 0 : m_quiet = true;
670 0 : m_progressBarRequested = false;
671 0 : continue;
672 : }
673 :
674 3240 : if (IsCalledFromCommandLine() && (arg == "-h" || arg == "--help"))
675 : {
676 8 : if (!steps.back().alg)
677 2 : steps.pop_back();
678 8 : if (steps.empty())
679 : {
680 2 : return GDALAlgorithm::ParseCommandLineArguments(args);
681 : }
682 : else
683 : {
684 6 : m_stepOnWhichHelpIsRequested = std::move(steps.back().alg);
685 6 : return true;
686 : }
687 : }
688 :
689 3232 : if (arg == "!" || arg == "|")
690 : {
691 638 : if (curStep.alg)
692 : {
693 622 : steps.resize(steps.size() + 1);
694 : }
695 : }
696 2594 : else if (arg == OPEN_NESTED_PIPELINE)
697 : {
698 56 : if (!curStep.alg)
699 : {
700 1 : ReportError(CE_Failure, CPLE_AppDefined,
701 : "Open bracket must be placed where an input "
702 : "dataset is expected");
703 1 : return false;
704 : }
705 55 : ++nNestLevel;
706 : }
707 2538 : else if (arg == CLOSE_NESTED_PIPELINE)
708 : {
709 1 : ReportError(CE_Failure, CPLE_AppDefined,
710 : "Closing bracket found without matching open bracket");
711 1 : return false;
712 : }
713 : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
714 2537 : else if (arg == "+step")
715 : {
716 8 : if (curStep.alg)
717 : {
718 4 : steps.resize(steps.size() + 1);
719 : }
720 : }
721 2529 : else if (arg.find("+gdal=") == 0)
722 : {
723 6 : const std::string algName = arg.substr(strlen("+gdal="));
724 6 : if (!SetCurStepAlg(curStep, algName, steps.size() == 1))
725 2 : return false;
726 : }
727 : #endif
728 2523 : else if (!curStep.alg)
729 : {
730 1112 : std::string algName = std::move(arg);
731 : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
732 1112 : if (!algName.empty() && algName[0] == '+')
733 2 : algName = algName.substr(1);
734 : #endif
735 1112 : if (!SetCurStepAlg(curStep, algName, steps.size() == 1))
736 12 : return false;
737 : }
738 : else
739 : {
740 1411 : if (curStep.alg->HasSubAlgorithms())
741 : {
742 : auto subAlg = std::unique_ptr<GDALPipelineStepAlgorithm>(
743 : cpl::down_cast<GDALPipelineStepAlgorithm *>(
744 3 : curStep.alg->InstantiateSubAlgorithm(arg).release()));
745 3 : if (!subAlg)
746 : {
747 1 : ReportError(CE_Failure, CPLE_AppDefined,
748 : "'%s' is a unknown sub-algorithm of '%s'",
749 1 : arg.c_str(), curStep.alg->GetName().c_str());
750 1 : return false;
751 : }
752 2 : curStep.isSubAlgorithm = true;
753 2 : subAlg->m_inputDatasetCanBeOmitted =
754 2 : steps.size() > 1 || !m_bExpectReadStep;
755 2 : curStep.alg = std::move(subAlg);
756 2 : continue;
757 : }
758 :
759 : #ifdef GDAL_PIPELINE_PROJ_NOSTALGIA
760 1417 : if (!arg.empty() && arg[0] == '+' &&
761 9 : arg.find(' ') == std::string::npos)
762 : {
763 6 : curStep.args.push_back("--" + arg.substr(1));
764 6 : continue;
765 : }
766 : #endif
767 1402 : curStep.args.push_back(std::move(arg));
768 : }
769 : }
770 :
771 478 : if (nNestLevel > 0)
772 : {
773 2 : if (forAutoComplete)
774 : {
775 1 : BuildNestedPipeline(steps.back().alg.get(), nestedPipelineArgs,
776 : forAutoComplete, pCurArgsForAutocomplete);
777 1 : return true;
778 : }
779 : else
780 : {
781 1 : ReportError(CE_Failure, CPLE_AppDefined,
782 : "Open bracket has no matching closing bracket");
783 1 : return false;
784 : }
785 : }
786 :
787 : // As we initially added a step without alg to bootstrap things, make
788 : // sure to remove it if it hasn't been filled, or the user has terminated
789 : // the pipeline with a '!' separator.
790 476 : if (!steps.back().alg)
791 17 : steps.pop_back();
792 :
793 476 : if (runExistingPipeline)
794 : {
795 : // Add a final "write" step if there is no explicit allowed last step
796 22 : if (!steps.empty() && !steps.back().alg->CanBeLastStep())
797 : {
798 18 : steps.resize(steps.size() + 1);
799 36 : steps.back().alg = GetStepAlg(
800 36 : std::string(GDALRasterWriteAlgorithm::NAME)
801 36 : .append(bIsGenericPipeline ? RASTER_SUFFIX : ""));
802 18 : steps.back().alg->m_inputDatasetCanBeOmitted = true;
803 : }
804 :
805 : // Remove "--output-format=stream" and "streamed_dataset" if found
806 22 : if (steps.back().alg->GetName() == GDALRasterWriteAlgorithm::NAME)
807 : {
808 30 : for (auto oIter = steps.back().args.begin();
809 30 : oIter != steps.back().args.end();)
810 : {
811 24 : if (*oIter == std::string("--")
812 16 : .append(GDAL_ARG_NAME_OUTPUT_FORMAT)
813 12 : .append("=stream") ||
814 16 : *oIter == std::string("--")
815 4 : .append(GDAL_ARG_NAME_OUTPUT)
816 32 : .append("=streamed_dataset") ||
817 4 : *oIter == "streamed_dataset")
818 : {
819 8 : oIter = steps.back().args.erase(oIter);
820 : }
821 : else
822 : {
823 0 : ++oIter;
824 : }
825 : }
826 : }
827 : }
828 :
829 476 : bool helpRequested = false;
830 476 : if (IsCalledFromCommandLine())
831 : {
832 245 : for (auto &step : steps)
833 163 : step.alg->SetCalledFromCommandLine();
834 :
835 481 : for (const std::string &v : args)
836 : {
837 399 : if (cpl::ends_with(v, "=?"))
838 3 : helpRequested = true;
839 : }
840 : }
841 :
842 476 : if (m_eLastStepAsWrite == StepConstraint::MUST_BE)
843 : {
844 77 : if (steps.size() < 2)
845 : {
846 23 : if (!steps.empty() && helpRequested)
847 : {
848 1 : steps.back().alg->ParseCommandLineArguments(steps.back().args);
849 1 : return false;
850 : }
851 :
852 22 : ReportError(CE_Failure, CPLE_AppDefined,
853 : "At least 2 steps must be provided");
854 22 : return false;
855 : }
856 :
857 54 : if (!steps.back().alg->CanBeLastStep())
858 : {
859 18 : if (helpRequested)
860 : {
861 2 : steps.back().alg->ParseCommandLineArguments(steps.back().args);
862 2 : return false;
863 : }
864 : }
865 : }
866 : else
867 : {
868 399 : if (steps.empty())
869 : {
870 4 : ReportError(CE_Failure, CPLE_AppDefined,
871 : "At least one step must be provided in %s pipeline.",
872 4 : m_bInnerPipeline ? "an inner" : "a");
873 4 : return false;
874 : }
875 : }
876 :
877 894 : std::vector<GDALPipelineStepAlgorithm *> stepAlgs;
878 1507 : for (const auto &step : steps)
879 1060 : stepAlgs.push_back(step.alg.get());
880 447 : if (!CheckFirstAndLastStep(stepAlgs, forAutoComplete))
881 10 : return false; // CheckFirstAndLastStep emits an error
882 :
883 1477 : for (auto &step : steps)
884 : {
885 1040 : step.alg->SetReferencePathForRelativePaths(
886 : GetReferencePathForRelativePaths());
887 : }
888 :
889 : const auto PropagateArgsFromPipeline =
890 736 : [this](GDALPipelineStepAlgorithm *alg)
891 : {
892 736 : const GDALAbstractPipelineAlgorithm *constThis = this;
893 8620 : for (auto &arg : alg->GetArgs())
894 : {
895 7884 : if (!arg->IsHidden())
896 : {
897 6728 : const auto pipelineArg = constThis->GetArg(arg->GetName());
898 6841 : if (pipelineArg && pipelineArg->IsExplicitlySet() &&
899 113 : pipelineArg->GetType() == arg->GetType())
900 : {
901 111 : arg->SetSkipIfAlreadySet(true);
902 111 : arg->SetFrom(*pipelineArg);
903 : }
904 : }
905 : }
906 736 : };
907 :
908 : // Propagate input parameters set at the pipeline level to the
909 : // "read" step
910 437 : if (m_bExpectReadStep)
911 : {
912 418 : PropagateArgsFromPipeline(steps.front().alg.get());
913 : }
914 :
915 : // Same with "write" step
916 852 : if (m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE &&
917 415 : steps.back().alg->CanBeLastStep())
918 : {
919 318 : PropagateArgsFromPipeline(steps.back().alg.get());
920 : }
921 :
922 437 : if (runExistingPipeline)
923 : {
924 22 : std::set<std::pair<Step *, std::string>> alreadyCleanedArgs;
925 :
926 297 : for (const auto &arg : GetArgs())
927 : {
928 809 : if (arg->IsUserProvided() ||
929 506 : ((arg->GetName() == GDAL_ARG_NAME_INPUT ||
930 484 : arg->GetName() == GDAL_ARG_NAME_INPUT_LAYER ||
931 462 : arg->GetName() == GDAL_ARG_NAME_OUTPUT ||
932 286 : arg->GetName() == GDAL_ARG_NAME_OUTPUT_FORMAT) &&
933 66 : arg->IsExplicitlySet()))
934 : {
935 : CPLStringList tokens(
936 36 : CSLTokenizeString2(arg->GetName().c_str(), ".", 0));
937 36 : std::string stepName;
938 36 : std::string stepArgName;
939 36 : if (tokens.size() == 1 && IsReadSpecificArgument(tokens[0]))
940 : {
941 3 : stepName = steps.front().alg->GetName();
942 3 : stepArgName = tokens[0];
943 : }
944 55 : else if (tokens.size() == 1 &&
945 22 : IsWriteSpecificArgument(tokens[0]))
946 : {
947 18 : stepName = steps.back().alg->GetName();
948 18 : stepArgName = tokens[0];
949 : }
950 15 : else if (tokens.size() == 2)
951 : {
952 10 : stepName = tokens[0];
953 10 : stepArgName = tokens[1];
954 : }
955 : else
956 : {
957 5 : if (tokens.size() == 1)
958 : {
959 4 : const Step *matchingStep = nullptr;
960 15 : for (auto &step : steps)
961 : {
962 12 : if (step.alg->GetArg(tokens[0]))
963 : {
964 4 : if (!matchingStep)
965 3 : matchingStep = &step;
966 : else
967 : {
968 1 : ReportError(
969 : CE_Failure, CPLE_AppDefined,
970 : "Ambiguous argument name '%s', because "
971 : "it is valid for several steps in the "
972 : "pipeline. It should be specified with "
973 : "the form "
974 : "<algorithm-name>.<argument-name>.",
975 : tokens[0]);
976 1 : return false;
977 : }
978 : }
979 : }
980 3 : if (!matchingStep)
981 : {
982 1 : ReportError(CE_Failure, CPLE_AppDefined,
983 : "No step in the pipeline has an "
984 : "argument named '%s'",
985 : tokens[0]);
986 1 : return false;
987 : }
988 2 : stepName = matchingStep->alg->GetName();
989 2 : stepArgName = tokens[0];
990 : }
991 : else
992 : {
993 1 : ReportError(
994 : CE_Failure, CPLE_AppDefined,
995 : "Invalid argument name '%s'. It should of the "
996 : "form <algorithm-name>.<argument-name>.",
997 1 : arg->GetName().c_str());
998 1 : return false;
999 : }
1000 : }
1001 33 : const auto nPosBracket = stepName.find('[');
1002 33 : int iRequestedStepIdx = -1;
1003 33 : if (nPosBracket != std::string::npos && stepName.back() == ']')
1004 : {
1005 : iRequestedStepIdx =
1006 3 : atoi(stepName.c_str() + nPosBracket + 1);
1007 3 : stepName.resize(nPosBracket);
1008 : }
1009 33 : int iMatchingStepIdx = 0;
1010 33 : Step *matchingStep = nullptr;
1011 133 : for (auto &step : steps)
1012 : {
1013 103 : if (step.alg->GetName() == stepName)
1014 : {
1015 35 : if (iRequestedStepIdx >= 0)
1016 : {
1017 5 : if (iRequestedStepIdx == iMatchingStepIdx)
1018 : {
1019 2 : matchingStep = &step;
1020 2 : break;
1021 : }
1022 3 : ++iMatchingStepIdx;
1023 : }
1024 30 : else if (matchingStep == nullptr)
1025 : {
1026 29 : matchingStep = &step;
1027 : }
1028 : else
1029 : {
1030 2 : ReportError(
1031 : CE_Failure, CPLE_AppDefined,
1032 : "Argument '%s' is ambiguous as there are "
1033 : "several '%s' steps in the pipeline. Qualify "
1034 : "it as '%s[<zero-based-index>]' to remove "
1035 : "ambiguity.",
1036 1 : arg->GetName().c_str(), stepName.c_str(),
1037 : stepName.c_str());
1038 1 : return false;
1039 : }
1040 : }
1041 : }
1042 32 : if (!matchingStep)
1043 : {
1044 4 : ReportError(CE_Failure, CPLE_AppDefined,
1045 : "Argument '%s' refers to a non-existing '%s' "
1046 : "step in the pipeline.",
1047 2 : arg->GetName().c_str(), tokens[0]);
1048 2 : return false;
1049 : }
1050 :
1051 30 : auto &step = *matchingStep;
1052 : std::string stepArgNameDashDash =
1053 90 : std::string("--").append(stepArgName);
1054 :
1055 60 : auto oKeyPair = std::make_pair(matchingStep, stepArgName);
1056 30 : if (!cpl::contains(alreadyCleanedArgs, oKeyPair))
1057 : {
1058 30 : alreadyCleanedArgs.insert(std::move(oKeyPair));
1059 :
1060 60 : std::vector<GDALAlgorithmArg *> positionalArgs;
1061 378 : for (auto &stepArg : step.alg->GetArgs())
1062 : {
1063 348 : if (stepArg->IsPositional())
1064 23 : positionalArgs.push_back(stepArg.get());
1065 : }
1066 :
1067 : // Remove step arguments that match the user override
1068 : const std::string stepArgNameDashDashEqual =
1069 60 : stepArgNameDashDash + '=';
1070 30 : size_t idxPositional = 0;
1071 44 : for (auto oIter = step.args.begin();
1072 44 : oIter != step.args.end();)
1073 : {
1074 14 : const auto &iterArgName = *oIter;
1075 14 : if (iterArgName == stepArgNameDashDash)
1076 : {
1077 1 : oIter = step.args.erase(oIter);
1078 1 : auto stepArg = step.alg->GetArg(stepArgName);
1079 1 : if (stepArg && stepArg->GetType() != GAAT_BOOLEAN)
1080 : {
1081 1 : if (oIter != step.args.end())
1082 1 : oIter = step.args.erase(oIter);
1083 : }
1084 : }
1085 13 : else if (cpl::starts_with(iterArgName,
1086 : stepArgNameDashDashEqual))
1087 : {
1088 3 : oIter = step.args.erase(oIter);
1089 : }
1090 10 : else if (!iterArgName.empty() && iterArgName[0] == '-')
1091 : {
1092 5 : const auto equalPos = iterArgName.find('=');
1093 10 : auto stepArg = step.alg->GetArg(
1094 : equalPos == std::string::npos
1095 10 : ? iterArgName
1096 : : iterArgName.substr(0, equalPos));
1097 5 : if (stepArg && stepArg->GetName() == stepArgName)
1098 : {
1099 1 : oIter = step.args.erase(oIter);
1100 1 : if (equalPos == std::string::npos &&
1101 2 : stepArg->GetType() != GAAT_BOOLEAN &&
1102 2 : oIter != step.args.end())
1103 : {
1104 1 : oIter = step.args.erase(oIter);
1105 : }
1106 : }
1107 : else
1108 : {
1109 4 : ++oIter;
1110 4 : if (stepArg && equalPos == std::string::npos &&
1111 8 : stepArg->GetType() != GAAT_BOOLEAN &&
1112 5 : oIter != step.args.end())
1113 : {
1114 1 : ++oIter;
1115 : }
1116 : }
1117 : }
1118 5 : else if (idxPositional < positionalArgs.size())
1119 : {
1120 4 : if (positionalArgs[idxPositional]->GetName() ==
1121 : stepArgName)
1122 : {
1123 2 : oIter = step.args.erase(oIter);
1124 : }
1125 : else
1126 : {
1127 2 : ++oIter;
1128 : }
1129 4 : ++idxPositional;
1130 : }
1131 : else
1132 : {
1133 1 : ++oIter;
1134 : }
1135 : }
1136 : }
1137 :
1138 30 : if (arg->IsUserProvided())
1139 : {
1140 : // Add user override
1141 11 : step.args.push_back(std::move(stepArgNameDashDash));
1142 11 : auto stepArg = step.alg->GetArg(stepArgName);
1143 11 : if (stepArg && stepArg->GetType() != GAAT_BOOLEAN)
1144 : {
1145 9 : step.args.push_back(arg->Get<std::string>());
1146 : }
1147 : }
1148 : }
1149 : }
1150 : }
1151 :
1152 431 : int nInitialDatasetType = 0;
1153 431 : if (bIsGenericPipeline)
1154 : {
1155 185 : if (!m_bExpectReadStep)
1156 : {
1157 19 : CPLAssert(m_inputDataset.size() == 1 &&
1158 : m_inputDataset[0].GetDatasetRef());
1159 : nInitialDatasetType =
1160 19 : GetDatasetType(m_inputDataset[0].GetDatasetRef());
1161 : }
1162 :
1163 : // Parse each step, but without running the validation
1164 185 : nDatasetType = nInitialDatasetType;
1165 185 : bool firstStep = nDatasetType == 0;
1166 :
1167 604 : for (auto &step : steps)
1168 : {
1169 427 : bool ret = false;
1170 427 : CPLErrorAccumulator oAccumulator;
1171 427 : bool hasTriedRaster = false;
1172 427 : if (nDatasetType == 0 || nDatasetType == GDAL_OF_RASTER)
1173 : {
1174 403 : hasTriedRaster = true;
1175 : [[maybe_unused]] auto context =
1176 806 : oAccumulator.InstallForCurrentScope();
1177 403 : step.alg->m_skipValidationInParseCommandLine = true;
1178 403 : ret = step.alg->ParseCommandLineArguments(step.args);
1179 403 : if (ret && nDatasetType == 0 && forAutoComplete)
1180 : {
1181 18 : ret = step.alg->ValidateArguments();
1182 33 : if (ret && firstStep &&
1183 15 : step.alg->m_inputDataset.size() == 1)
1184 : {
1185 15 : auto poDS = step.alg->m_inputDataset[0].GetDatasetRef();
1186 15 : if (poDS && poDS->GetLayerCount() > 0)
1187 9 : ret = false;
1188 : }
1189 3 : else if (!ret && firstStep)
1190 3 : ret = true;
1191 403 : }
1192 : }
1193 32 : else if (!m_bExpectReadStep &&
1194 8 : nDatasetType == step.alg->GetInputType())
1195 : {
1196 5 : step.alg->m_skipValidationInParseCommandLine = true;
1197 5 : ret = step.alg->ParseCommandLineArguments(step.args);
1198 5 : if (!ret)
1199 1 : return false;
1200 : }
1201 :
1202 426 : if (!ret)
1203 : {
1204 : auto algVector =
1205 44 : GetStepAlg(step.alg->GetName() + VECTOR_SUFFIX);
1206 82 : if (algVector &&
1207 82 : (nDatasetType == 0 || nDatasetType == GDAL_OF_VECTOR))
1208 : {
1209 32 : step.alg = std::move(algVector);
1210 32 : step.alg->m_inputDatasetCanBeOmitted =
1211 32 : !firstStep || !m_bExpectReadStep;
1212 32 : step.alg->m_skipValidationInParseCommandLine = true;
1213 32 : ret = step.alg->ParseCommandLineArguments(step.args);
1214 32 : if (ret)
1215 : {
1216 38 : step.alg->SetCallPath({step.alg->GetName()});
1217 19 : step.alg->SetReferencePathForRelativePaths(
1218 : GetReferencePathForRelativePaths());
1219 19 : step.alreadyChangedType = true;
1220 : }
1221 13 : else if (!forAutoComplete)
1222 6 : return false;
1223 : }
1224 38 : if (!ret && hasTriedRaster && !forAutoComplete)
1225 : {
1226 2 : for (const auto &sError : oAccumulator.GetErrors())
1227 : {
1228 1 : CPLError(sError.type, sError.no, "%s",
1229 : sError.msg.c_str());
1230 : }
1231 1 : return false;
1232 : }
1233 : }
1234 419 : if (ret && forAutoComplete)
1235 28 : nDatasetType = step.alg->GetOutputType();
1236 419 : firstStep = false;
1237 : }
1238 : }
1239 : else
1240 : {
1241 804 : for (auto &step : steps)
1242 : {
1243 573 : step.alg->m_skipValidationInParseCommandLine = true;
1244 593 : if (!step.alg->ParseCommandLineArguments(step.args) &&
1245 20 : !forAutoComplete)
1246 15 : return false;
1247 : }
1248 : }
1249 :
1250 : // Evaluate "input" argument of "read" step, together with the "output"
1251 : // argument of the "write" step, in case they point to the same dataset.
1252 408 : auto inputArg = steps.front().alg->GetArg(GDAL_ARG_NAME_INPUT);
1253 791 : if (inputArg && inputArg->IsExplicitlySet() &&
1254 1199 : inputArg->GetType() == GAAT_DATASET_LIST &&
1255 383 : inputArg->Get<std::vector<GDALArgDatasetValue>>().size() == 1)
1256 : {
1257 378 : int nCountChangeFieldTypeStepsToBeRemoved = 0;
1258 378 : std::string osTmpJSONFilename;
1259 :
1260 : // Check if there are steps like change-field-type just after the read
1261 : // step. If so, we can convert them into a OGR_SCHEMA open option for
1262 : // drivers that support it.
1263 378 : auto &inputVals = inputArg->Get<std::vector<GDALArgDatasetValue>>();
1264 1014 : if (!inputVals[0].GetDatasetRef() && steps.size() >= 2 &&
1265 1014 : steps[0].alg->GetName() == GDALVectorReadAlgorithm::NAME &&
1266 292 : !steps.back().alg->IsGDALGOutput())
1267 : {
1268 : auto openOptionArgs =
1269 280 : steps.front().alg->GetArg(GDAL_ARG_NAME_OPEN_OPTION);
1270 560 : if (openOptionArgs && !openOptionArgs->IsExplicitlySet() &&
1271 280 : openOptionArgs->GetType() == GAAT_STRING_LIST)
1272 : {
1273 : const auto &openOptionVals =
1274 280 : openOptionArgs->Get<std::vector<std::string>>();
1275 560 : if (CPLStringList(openOptionVals)
1276 280 : .FetchNameValue("OGR_SCHEMA") == nullptr)
1277 : {
1278 560 : CPLJSONArray oLayers;
1279 289 : for (size_t iStep = 1; iStep < steps.size(); ++iStep)
1280 : {
1281 : auto oObj =
1282 286 : steps[iStep].alg->Get_OGR_SCHEMA_OpenOption_Layer();
1283 286 : if (!oObj.IsValid())
1284 277 : break;
1285 9 : oLayers.Add(oObj);
1286 9 : ++nCountChangeFieldTypeStepsToBeRemoved;
1287 : }
1288 :
1289 280 : if (nCountChangeFieldTypeStepsToBeRemoved > 0)
1290 : {
1291 7 : CPLJSONDocument oDoc;
1292 7 : oDoc.GetRoot().Set("layers", oLayers);
1293 : osTmpJSONFilename =
1294 7 : VSIMemGenerateHiddenFilename(nullptr);
1295 : // CPLDebug("GDAL", "OGR_SCHEMA: %s", oDoc.SaveAsString().c_str());
1296 7 : oDoc.Save(osTmpJSONFilename);
1297 :
1298 14 : openOptionArgs->Set(std::vector<std::string>{
1299 14 : std::string("@OGR_SCHEMA=")
1300 14 : .append(osTmpJSONFilename)});
1301 : }
1302 : }
1303 : }
1304 : }
1305 :
1306 756 : const bool bOK = steps.front().alg->ProcessDatasetArg(
1307 756 : inputArg, steps.back().alg.get()) ||
1308 378 : forAutoComplete;
1309 :
1310 378 : if (!osTmpJSONFilename.empty())
1311 7 : VSIUnlink(osTmpJSONFilename.c_str());
1312 :
1313 378 : if (!bOK)
1314 : {
1315 7 : return false;
1316 : }
1317 :
1318 : // Now check if the driver of the input dataset actually supports
1319 : // the OGR_SCHEMA open option. If so, we can remove the steps from
1320 : // the pipeline
1321 371 : if (nCountChangeFieldTypeStepsToBeRemoved)
1322 : {
1323 7 : if (auto poDS = inputVals[0].GetDatasetRef())
1324 : {
1325 7 : if (auto poDriver = poDS->GetDriver())
1326 : {
1327 : const char *pszOpenOptionList =
1328 7 : poDriver->GetMetadataItem(GDAL_DMD_OPENOPTIONLIST);
1329 7 : if (pszOpenOptionList &&
1330 7 : strstr(pszOpenOptionList, "OGR_SCHEMA"))
1331 : {
1332 1 : CPLDebug("GDAL",
1333 : "Merging %d step(s) as OGR_SCHEMA open option",
1334 : nCountChangeFieldTypeStepsToBeRemoved);
1335 1 : steps.erase(steps.begin() + 1,
1336 1 : steps.begin() + 1 +
1337 3 : nCountChangeFieldTypeStepsToBeRemoved);
1338 : }
1339 : }
1340 : }
1341 : }
1342 : }
1343 :
1344 401 : if (bIsGenericPipeline)
1345 : {
1346 176 : int nLastStepOutputType = nInitialDatasetType;
1347 176 : if (m_bExpectReadStep)
1348 : {
1349 158 : nLastStepOutputType = GDAL_OF_VECTOR;
1350 158 : if (steps.front().alg->GetName() !=
1351 321 : std::string(GDALRasterReadAlgorithm::NAME) &&
1352 5 : steps.front().alg->GetOutputType() == GDAL_OF_RASTER)
1353 : {
1354 1 : nLastStepOutputType = GDAL_OF_RASTER;
1355 : }
1356 : else
1357 : {
1358 157 : auto &inputDatasets = steps.front().alg->GetInputDatasets();
1359 157 : if (!inputDatasets.empty())
1360 : {
1361 152 : auto poSrcDS = inputDatasets[0].GetDatasetRef();
1362 152 : if (poSrcDS)
1363 : {
1364 149 : if (poSrcDS->GetRasterCount() != 0)
1365 95 : nLastStepOutputType = GDAL_OF_RASTER;
1366 : }
1367 : }
1368 : }
1369 : }
1370 :
1371 168 : for (size_t i =
1372 158 : ((m_bExpectReadStep && steps[0].alg->GetOutputType() != 0)
1373 334 : ? 1
1374 176 : : 0);
1375 344 : !forAutoComplete && i < steps.size(); ++i)
1376 : {
1377 198 : auto &step = steps[i];
1378 :
1379 389 : if (!step.alreadyChangedType && !step.isSubAlgorithm &&
1380 389 : GetStepAlg(step.alg->GetName()) == nullptr)
1381 : {
1382 : // GetInputDatasetType() is to deal with pipelines like
1383 : // gdal pipeline read input_vector ! clip --input raster_dataset --like _PIPE_ ! write output_raster
1384 : const int nThisDatasetType =
1385 117 : GetInputDatasetType(step.alg.get());
1386 117 : const int nThisStepType =
1387 117 : nThisDatasetType ? nThisDatasetType : nLastStepOutputType;
1388 :
1389 234 : if (step.alg->GetInputType() != 0 &&
1390 117 : nThisStepType != step.alg->GetInputType())
1391 : {
1392 27 : auto newAlg = GetStepAlg(step.alg->GetName() +
1393 : (nThisStepType == GDAL_OF_RASTER
1394 : ? RASTER_SUFFIX
1395 27 : : VECTOR_SUFFIX));
1396 27 : CPLAssert(newAlg);
1397 :
1398 : const bool maybeWriteStep =
1399 47 : (i == steps.size() - 1 &&
1400 20 : m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE);
1401 :
1402 27 : if (!CopyStepAlgorithmFromAnother(
1403 27 : newAlg.get(), step.alg.get(), maybeWriteStep))
1404 1 : return false;
1405 :
1406 26 : newAlg->m_inputDatasetCanBeOmitted =
1407 26 : i > 0 || !m_bExpectReadStep;
1408 26 : step.alg = std::move(newAlg);
1409 26 : step.alreadyChangedType = true;
1410 : }
1411 : }
1412 :
1413 197 : if (i > 0)
1414 : {
1415 : bool emitError =
1416 332 : (step.alg->GetInputType() != 0 &&
1417 157 : step.alg->GetInputType() != nLastStepOutputType);
1418 :
1419 : // Check if a dataset argument, which has as value the
1420 : // placeholder value, has the same dataset type as the output
1421 : // of the last step
1422 2529 : for (const auto &arg : step.alg->GetArgs())
1423 : {
1424 6891 : if (!arg->IsOutput() &&
1425 4527 : (arg->GetType() == GAAT_DATASET ||
1426 2248 : arg->GetType() == GAAT_DATASET_LIST))
1427 : {
1428 225 : if (arg->GetType() == GAAT_DATASET)
1429 : {
1430 31 : if (arg->Get<GDALArgDatasetValue>().GetName() ==
1431 : GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
1432 : {
1433 8 : if ((arg->GetDatasetType() &
1434 8 : nLastStepOutputType) != 0)
1435 : {
1436 8 : emitError = false;
1437 8 : break;
1438 : }
1439 : }
1440 : }
1441 : else
1442 : {
1443 194 : CPLAssert(arg->GetType() == GAAT_DATASET_LIST);
1444 : auto &val =
1445 194 : arg->Get<std::vector<GDALArgDatasetValue>>();
1446 218 : if (val.size() == 1 &&
1447 24 : val[0].GetName() ==
1448 : GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
1449 : {
1450 3 : if ((arg->GetDatasetType() &
1451 3 : nLastStepOutputType) != 0)
1452 : {
1453 2 : emitError = false;
1454 2 : break;
1455 : }
1456 : }
1457 : }
1458 : }
1459 : }
1460 175 : if (emitError)
1461 : {
1462 12 : ReportError(
1463 : CE_Failure, CPLE_AppDefined,
1464 : "Step '%s' expects a %s input dataset, but "
1465 : "previous step '%s' "
1466 : "generates a %s output dataset",
1467 3 : step.alg->GetName().c_str(),
1468 3 : step.alg->GetInputType() == GDAL_OF_RASTER ? "raster"
1469 1 : : step.alg->GetInputType() == GDAL_OF_VECTOR
1470 1 : ? "vector"
1471 : : "unknown",
1472 3 : steps[i - 1].alg->GetName().c_str(),
1473 3 : nLastStepOutputType == GDAL_OF_RASTER ? "raster"
1474 2 : : nLastStepOutputType == GDAL_OF_VECTOR ? "vector"
1475 : : "unknown");
1476 3 : return false;
1477 : }
1478 : }
1479 :
1480 194 : nLastStepOutputType = step.alg->GetOutputType();
1481 194 : if (!forAutoComplete && nLastStepOutputType == 0)
1482 : {
1483 : // If this step has no precise output dataset (unique instance
1484 : // at time of writing is 'external'), we stop trying to fix
1485 : // the raster/vector nature of ambiguous steps for now, and
1486 : // defer doing that during pipeline execution itself.
1487 26 : m_nFirstStepWithUnknownInputType = static_cast<int>(i + 1);
1488 26 : break;
1489 : }
1490 : }
1491 : }
1492 :
1493 397 : int iStep = 0;
1494 1289 : for (const auto &step : steps)
1495 : {
1496 921 : if (iStep == m_nFirstStepWithUnknownInputType)
1497 20 : break;
1498 901 : if (!step.alg->ValidateArguments() && !forAutoComplete)
1499 9 : return false;
1500 892 : ++iStep;
1501 : }
1502 :
1503 1300 : for (auto &step : steps)
1504 912 : m_steps.push_back(std::move(step.alg));
1505 :
1506 388 : return true;
1507 : }
1508 :
1509 : /************************************************************************/
1510 : /* GDALAbstractPipelineAlgorithm::BuildNestedPipeline() */
1511 : /************************************************************************/
1512 :
1513 : /** Build a nested pipeline
1514 : *
1515 : * @param curAlg Current algorithm for which the nested pipeline will be a child.
1516 : * e.g in "gdal pipeline read ... ! clip --like [ ... ]",
1517 : * curAlg is "clip".
1518 : * @param nestedPipelineArgs Arguments of the nested pipeline, i.e. values
1519 : * between square brackets.
1520 : * @param forAutoComplete true if this method is called from GetAutoComplete()
1521 : * @param[out] pCurArgsForAutocomplete Pointer to a vector of string, or null.
1522 : * If provided, it will contain the arguments
1523 : * of the active pipeline. Useful for
1524 : * completion in nested pipelines.
1525 : */
1526 53 : std::string GDALAbstractPipelineAlgorithm::BuildNestedPipeline(
1527 : GDALPipelineStepAlgorithm *curAlg,
1528 : std::vector<std::string> &nestedPipelineArgs, bool forAutoComplete,
1529 : std::vector<std::string> *pCurArgsForAutocomplete)
1530 : {
1531 53 : std::string datasetNameOut;
1532 53 : CPLAssert(curAlg);
1533 :
1534 106 : auto nestedPipeline = CreateNestedPipeline();
1535 53 : if (curAlg->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
1536 27 : nestedPipeline->m_bExpectReadStep = false;
1537 : else
1538 26 : nestedPipeline->m_eLastStepAsWrite = StepConstraint::CAN_NOT_BE;
1539 53 : nestedPipeline->m_executionForStreamOutput = m_executionForStreamOutput;
1540 53 : if (IsCalledFromCommandLine())
1541 6 : nestedPipeline->SetCalledFromCommandLine();
1542 53 : nestedPipeline->SetReferencePathForRelativePaths(
1543 : GetReferencePathForRelativePaths());
1544 :
1545 106 : std::string argsStr = OPEN_NESTED_PIPELINE;
1546 208 : for (const std::string &str : nestedPipelineArgs)
1547 : {
1548 155 : argsStr += ' ';
1549 155 : argsStr += GDALAlgorithmArg::GetEscapedString(str);
1550 : }
1551 53 : argsStr += ' ';
1552 53 : argsStr += CLOSE_NESTED_PIPELINE;
1553 :
1554 53 : if (curAlg->GetName() != GDALTeeStepAlgorithmAbstract::NAME)
1555 : {
1556 26 : if (!nestedPipeline->ParseCommandLineArguments(
1557 47 : nestedPipelineArgs, forAutoComplete, pCurArgsForAutocomplete) ||
1558 21 : (!forAutoComplete && !nestedPipeline->Run()))
1559 : {
1560 6 : return datasetNameOut;
1561 : }
1562 20 : auto poDS = nestedPipeline->GetOutputDataset().GetDatasetRef();
1563 20 : if (!poDS)
1564 : {
1565 : // That shouldn't happen normally for well-behaved algorithms, but
1566 : // it doesn't hurt checking.
1567 2 : ReportError(CE_Failure, CPLE_AppDefined,
1568 : "Nested pipeline does not generate an output dataset");
1569 2 : return datasetNameOut;
1570 : }
1571 : datasetNameOut =
1572 18 : CPLSPrintf("$$nested_pipeline_%p$$", nestedPipeline.get());
1573 18 : curAlg->m_oMapDatasetNameToDataset[datasetNameOut] = poDS;
1574 :
1575 18 : poDS->SetDescription(argsStr.c_str());
1576 18 : auto poVRTDataset = dynamic_cast<VRTDataset *>(poDS);
1577 18 : if (poVRTDataset)
1578 2 : poVRTDataset->SetWritable(false);
1579 : }
1580 :
1581 45 : m_apoNestedPipelines.emplace_back(std::move(nestedPipeline));
1582 :
1583 45 : if (curAlg->GetName() == GDALTeeStepAlgorithmAbstract::NAME)
1584 : {
1585 27 : auto teeAlg = dynamic_cast<GDALTeeStepAlgorithmAbstract *>(curAlg);
1586 27 : if (teeAlg)
1587 : {
1588 27 : datasetNameOut = std::move(argsStr);
1589 27 : if (!teeAlg->BindFilename(datasetNameOut,
1590 27 : m_apoNestedPipelines.back().get(),
1591 : nestedPipelineArgs))
1592 : {
1593 1 : ReportError(CE_Failure, CPLE_AppDefined,
1594 : "Another identical nested pipeline exists");
1595 1 : datasetNameOut.clear();
1596 : }
1597 : }
1598 : }
1599 :
1600 45 : nestedPipelineArgs.clear();
1601 :
1602 45 : return datasetNameOut;
1603 : }
1604 :
1605 : /************************************************************************/
1606 : /* GDALAbstractPipelineAlgorithm::GetAutoComplete() */
1607 : /************************************************************************/
1608 :
1609 : std::vector<std::string>
1610 54 : GDALAbstractPipelineAlgorithm::GetAutoComplete(std::vector<std::string> &argsIn,
1611 : bool lastWordIsComplete,
1612 : bool showAllOptions)
1613 : {
1614 108 : std::vector<std::string> args;
1615 : {
1616 108 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1617 54 : ParseCommandLineArguments(argsIn, /*forAutoComplete=*/true, &args);
1618 : }
1619 : VSIStatBufL sStat;
1620 60 : if (!m_pipeline.empty() && VSIStatL(m_pipeline.c_str(), &sStat) == 0 &&
1621 60 : !m_steps.empty() && !args.empty())
1622 : {
1623 12 : std::map<std::string, std::vector<GDALAlgorithm *>> mapSteps;
1624 26 : for (const auto &step : m_steps)
1625 : {
1626 20 : mapSteps[step->GetName()].push_back(step.get());
1627 : }
1628 :
1629 12 : std::vector<std::string> ret;
1630 6 : const auto &lastArg = args.back();
1631 18 : if (!lastArg.empty() && lastArg[0] == '-' &&
1632 18 : lastArg.find('=') == std::string::npos && !lastWordIsComplete)
1633 : {
1634 13 : for (const auto &step : m_steps)
1635 : {
1636 : const int iterCount =
1637 10 : static_cast<int>(mapSteps[step->GetName()].size());
1638 22 : for (int i = 0; i < iterCount; ++i)
1639 : {
1640 171 : for (const auto &arg : step->GetArgs())
1641 : {
1642 297 : if (!arg->IsHiddenForCLI() &&
1643 138 : arg->GetCategory() != GAAC_COMMON)
1644 : {
1645 200 : std::string s = std::string("--");
1646 200 : if (!((step->GetName() ==
1647 11 : GDALRasterReadAlgorithm::NAME &&
1648 11 : IsReadSpecificArgument(
1649 11 : arg->GetName().c_str())) ||
1650 89 : (step->GetName() ==
1651 31 : GDALRasterWriteAlgorithm::NAME &&
1652 31 : IsWriteSpecificArgument(
1653 31 : arg->GetName().c_str()))))
1654 : {
1655 66 : s += step->GetName();
1656 66 : if (iterCount > 1)
1657 : {
1658 36 : s += '[';
1659 36 : s += std::to_string(i);
1660 36 : s += ']';
1661 : }
1662 66 : s += '.';
1663 : }
1664 100 : s += arg->GetName();
1665 100 : if (arg->GetType() == GAAT_BOOLEAN)
1666 22 : ret.push_back(std::move(s));
1667 : else
1668 78 : ret.push_back(s + "=");
1669 : }
1670 : }
1671 : }
1672 : }
1673 : }
1674 6 : else if (cpl::starts_with(lastArg, "--") &&
1675 6 : lastArg.find('=') != std::string::npos && !lastWordIsComplete)
1676 : {
1677 3 : const auto nDotPos = lastArg.find('.');
1678 6 : std::string stepName;
1679 6 : std::string argName;
1680 3 : int idx = 0;
1681 3 : if (nDotPos != std::string::npos)
1682 : {
1683 1 : stepName = lastArg.substr(strlen("--"), nDotPos - strlen("--"));
1684 1 : const auto nBracketPos = stepName.find('[');
1685 1 : if (nBracketPos != std::string::npos)
1686 : {
1687 1 : idx = atoi(stepName.c_str() + nBracketPos + 1);
1688 1 : stepName.resize(nBracketPos);
1689 : }
1690 1 : argName = "--" + lastArg.substr(nDotPos + 1);
1691 : }
1692 : else
1693 : {
1694 2 : argName = lastArg;
1695 7 : for (const char *prefix : apszReadParametersPrefixOmitted)
1696 : {
1697 6 : if (cpl::starts_with(lastArg.substr(strlen("--")),
1698 12 : std::string(prefix) + "="))
1699 : {
1700 1 : stepName = GDALRasterReadAlgorithm::NAME;
1701 1 : break;
1702 : }
1703 : }
1704 :
1705 13 : for (const char *prefix : apszWriteParametersPrefixOmitted)
1706 : {
1707 12 : if (cpl::starts_with(lastArg.substr(strlen("--")),
1708 24 : std::string(prefix) + "="))
1709 : {
1710 1 : stepName = GDALRasterWriteAlgorithm::NAME;
1711 1 : break;
1712 : }
1713 : }
1714 : }
1715 :
1716 3 : auto iter = mapSteps.find(stepName);
1717 6 : if (iter != mapSteps.end() && idx >= 0 &&
1718 3 : static_cast<size_t>(idx) < iter->second.size())
1719 : {
1720 3 : auto &step = iter->second[idx];
1721 3 : std::vector<std::string> subArgs;
1722 34 : for (const auto &arg : step->GetArgs())
1723 : {
1724 62 : std::string strArg;
1725 35 : if (arg->IsExplicitlySet() &&
1726 4 : arg->Serialize(strArg, /* absolutePath=*/false))
1727 : {
1728 4 : subArgs.push_back(std::move(strArg));
1729 : }
1730 : }
1731 3 : subArgs.push_back(std::move(argName));
1732 3 : ret = step->GetAutoComplete(subArgs, lastWordIsComplete,
1733 3 : showAllOptions);
1734 : }
1735 : }
1736 6 : return ret;
1737 : }
1738 : else
1739 : {
1740 96 : std::vector<std::string> ret;
1741 96 : std::set<std::string> setSuggestions;
1742 48 : if (args.size() <= 1)
1743 : {
1744 433 : for (const std::string &name : GetStepRegistry().GetNames())
1745 : {
1746 427 : auto alg = GetStepRegistry().Instantiate(name);
1747 : auto stepAlg =
1748 427 : dynamic_cast<GDALPipelineStepAlgorithm *>(alg.get());
1749 427 : if (stepAlg && stepAlg->CanBeFirstStep())
1750 : {
1751 : std::string suggestionName =
1752 54 : CPLString(name)
1753 108 : .replaceAll(RASTER_SUFFIX, "")
1754 108 : .replaceAll(VECTOR_SUFFIX, "");
1755 54 : if (!cpl::contains(setSuggestions, suggestionName))
1756 : {
1757 51 : if (!args.empty() && suggestionName == args[0])
1758 3 : return {};
1759 79 : if (args.empty() ||
1760 31 : cpl::starts_with(suggestionName, args[0]))
1761 : {
1762 20 : setSuggestions.insert(suggestionName);
1763 20 : ret.push_back(std::move(suggestionName));
1764 : }
1765 : }
1766 : }
1767 : }
1768 : }
1769 : else
1770 : {
1771 39 : int nDatasetType = GetInputType();
1772 39 : constexpr int MIXED_TYPE = GDAL_OF_RASTER | GDAL_OF_VECTOR;
1773 39 : const bool isMixedTypePipeline = nDatasetType == MIXED_TYPE;
1774 39 : std::string lastStep = args[0];
1775 39 : std::vector<std::string> lastArgs;
1776 39 : bool firstStep = true;
1777 39 : bool foundSlowStep = false;
1778 146 : for (size_t i = 1; i < args.size(); ++i)
1779 : {
1780 67 : if (firstStep && isMixedTypePipeline &&
1781 199 : nDatasetType == MIXED_TYPE && !args[i].empty() &&
1782 25 : args[i][0] != '-')
1783 : {
1784 44 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1785 : auto poDS = std::unique_ptr<GDALDataset>(
1786 44 : GDALDataset::Open(args[i].c_str()));
1787 22 : if (poDS)
1788 : {
1789 11 : const int nThisDatasetType = GetDatasetType(poDS.get());
1790 11 : if (nThisDatasetType)
1791 11 : nDatasetType = nThisDatasetType;
1792 : }
1793 : }
1794 107 : lastArgs.push_back(args[i]);
1795 107 : if (i + 1 < args.size() && args[i] == "!")
1796 : {
1797 28 : firstStep = false;
1798 28 : ++i;
1799 28 : lastArgs.clear();
1800 28 : lastStep = args[i];
1801 56 : auto curAlg = GetStepAlg(lastStep);
1802 28 : if (isMixedTypePipeline && !curAlg)
1803 : {
1804 11 : if (nDatasetType == GDAL_OF_RASTER)
1805 2 : curAlg = GetStepAlg(lastStep + RASTER_SUFFIX);
1806 9 : else if (nDatasetType == GDAL_OF_VECTOR)
1807 3 : curAlg = GetStepAlg(lastStep + VECTOR_SUFFIX);
1808 : }
1809 28 : if (curAlg)
1810 : {
1811 19 : foundSlowStep =
1812 36 : foundSlowStep ||
1813 17 : !curAlg->IsNativelyStreamingCompatible();
1814 19 : nDatasetType = curAlg->GetOutputType();
1815 : }
1816 : }
1817 : }
1818 :
1819 73 : if (args.back() == "!" ||
1820 73 : (args[args.size() - 2] == "!" && !GetStepAlg(args.back()) &&
1821 42 : !GetStepAlg(args.back() + RASTER_SUFFIX) &&
1822 42 : !GetStepAlg(args.back() + VECTOR_SUFFIX)))
1823 : {
1824 631 : for (const std::string &name : GetStepRegistry().GetNames())
1825 : {
1826 620 : auto alg = GetStepRegistry().Instantiate(name);
1827 : auto stepAlg =
1828 620 : dynamic_cast<GDALPipelineStepAlgorithm *>(alg.get());
1829 620 : if (stepAlg && isMixedTypePipeline &&
1830 1240 : nDatasetType != MIXED_TYPE &&
1831 172 : stepAlg->GetInputType() != nDatasetType)
1832 : {
1833 87 : continue;
1834 : }
1835 533 : if (stepAlg && !stepAlg->CanBeFirstStep())
1836 : {
1837 : std::string suggestionName =
1838 473 : CPLString(name)
1839 946 : .replaceAll(RASTER_SUFFIX, "")
1840 1419 : .replaceAll(VECTOR_SUFFIX, "");
1841 473 : if (!cpl::contains(setSuggestions, suggestionName))
1842 : {
1843 455 : setSuggestions.insert(suggestionName);
1844 455 : ret.push_back(std::move(suggestionName));
1845 : }
1846 : }
1847 : }
1848 : }
1849 : else
1850 : {
1851 28 : if (!foundSlowStep)
1852 : {
1853 : // Try to run the pipeline so that the last step gets its
1854 : // input dataset.
1855 24 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1856 24 : GDALPipelineStepRunContext ctxt;
1857 24 : RunStep(ctxt);
1858 38 : if (!m_steps.empty() &&
1859 14 : m_steps.back()->GetName() == lastStep)
1860 : {
1861 14 : return m_steps.back()->GetAutoComplete(
1862 : lastArgs, lastWordIsComplete,
1863 14 : /* showAllOptions = */ false);
1864 : }
1865 : }
1866 :
1867 28 : auto curAlg = GetStepAlg(lastStep);
1868 14 : if (isMixedTypePipeline && !curAlg)
1869 : {
1870 5 : if (nDatasetType == GDAL_OF_RASTER)
1871 1 : curAlg = GetStepAlg(lastStep + RASTER_SUFFIX);
1872 4 : else if (nDatasetType == GDAL_OF_VECTOR)
1873 0 : curAlg = GetStepAlg(lastStep + VECTOR_SUFFIX);
1874 : else
1875 : {
1876 8 : for (const char *suffix :
1877 12 : {RASTER_SUFFIX, VECTOR_SUFFIX})
1878 : {
1879 8 : curAlg = GetStepAlg(lastStep + suffix);
1880 8 : if (curAlg)
1881 : {
1882 48 : for (const auto &v : curAlg->GetAutoComplete(
1883 : lastArgs, lastWordIsComplete,
1884 88 : /* showAllOptions = */ false))
1885 : {
1886 40 : if (!cpl::contains(setSuggestions, v))
1887 : {
1888 29 : setSuggestions.insert(v);
1889 29 : ret.push_back(std::move(v));
1890 : }
1891 : }
1892 : }
1893 : }
1894 4 : curAlg.reset();
1895 : }
1896 : }
1897 14 : if (curAlg)
1898 : {
1899 20 : ret = curAlg->GetAutoComplete(lastArgs, lastWordIsComplete,
1900 10 : /* showAllOptions = */ false);
1901 : }
1902 : }
1903 : }
1904 31 : return ret;
1905 : }
1906 : }
1907 :
1908 : /************************************************************************/
1909 : /* GDALAbstractPipelineAlgorithm::SaveGDALGIntoFileOrString() */
1910 : /************************************************************************/
1911 :
1912 : /** Save the pipeline either into the file of name outFilename, if
1913 : * outFilename is not empty, or into the output string outString if
1914 : * outFilename is empty.
1915 : */
1916 13 : bool GDALAbstractPipelineAlgorithm::SaveGDALGIntoFileOrString(
1917 : const std::string &outFilename, std::string &outString) const
1918 : {
1919 26 : std::string osCommandLine;
1920 :
1921 48 : for (const auto &path : GDALAlgorithm::m_callPath)
1922 : {
1923 35 : if (!osCommandLine.empty())
1924 22 : osCommandLine += ' ';
1925 35 : osCommandLine += path;
1926 : }
1927 :
1928 : // Do not include the last step
1929 35 : for (size_t i = 0; i + 1 < m_steps.size(); ++i)
1930 : {
1931 23 : const auto &step = m_steps[i];
1932 23 : if (!step->IsNativelyStreamingCompatible())
1933 : {
1934 3 : GDALAlgorithm::ReportError(
1935 : CE_Warning, CPLE_AppDefined,
1936 : "Step %s is not natively streaming compatible, and "
1937 : "may cause significant processing time at opening",
1938 3 : step->GDALAlgorithm::GetName().c_str());
1939 : }
1940 :
1941 23 : if (i > 0)
1942 10 : osCommandLine += " !";
1943 46 : for (const auto &path : step->GDALAlgorithm::m_callPath)
1944 : {
1945 23 : if (!osCommandLine.empty())
1946 23 : osCommandLine += ' ';
1947 23 : osCommandLine += path;
1948 : }
1949 :
1950 246 : for (const auto &arg : step->GetArgs())
1951 : {
1952 224 : if (arg->IsExplicitlySet())
1953 : {
1954 22 : osCommandLine += ' ';
1955 22 : std::string strArg;
1956 22 : if (!arg->Serialize(strArg, /* absolutePath=*/false))
1957 : {
1958 1 : CPLError(CE_Failure, CPLE_AppDefined,
1959 : "Cannot serialize argument %s",
1960 1 : arg->GetName().c_str());
1961 1 : return false;
1962 : }
1963 21 : osCommandLine += strArg;
1964 : }
1965 : }
1966 : }
1967 :
1968 12 : return GDALAlgorithm::SaveGDALG(outFilename, outString, osCommandLine);
1969 : }
1970 :
1971 : /************************************************************************/
1972 : /* RunStepDealWithGDALGJson() */
1973 : /************************************************************************/
1974 :
1975 : GDALAbstractPipelineAlgorithm::RunStepState
1976 375 : GDALAbstractPipelineAlgorithm::RunStepDealWithGDALGJson()
1977 : {
1978 : // Handle output to GDALG file
1979 750 : if (!m_steps.empty() &&
1980 375 : m_steps.back()->GetName() == GDALRasterWriteAlgorithm::NAME)
1981 : {
1982 220 : const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
1983 : const auto outputFormatArg =
1984 220 : m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT_FORMAT);
1985 440 : if (outputArg && outputArg->GetType() == GAAT_DATASET &&
1986 220 : outputArg->IsExplicitlySet())
1987 : {
1988 : const std::string &outputFileName =
1989 220 : outputArg->Get<GDALArgDatasetValue>().GetName();
1990 220 : if (m_steps.back()->IsGDALGOutput())
1991 : {
1992 12 : std::string outStringUnused;
1993 12 : return SaveGDALGIntoFileOrString(outputFileName,
1994 : outStringUnused)
1995 12 : ? RunStepState::PROCESSED
1996 12 : : RunStepState::ERROR;
1997 : }
1998 :
1999 : bool isVRTOutput;
2000 416 : if (outputFormatArg && outputFormatArg->GetType() == GAAT_STRING &&
2001 208 : outputFormatArg->IsExplicitlySet())
2002 : {
2003 80 : const auto &val = outputFormatArg->Get<std::string>();
2004 80 : isVRTOutput = EQUAL(val.c_str(), "vrt");
2005 : }
2006 : else
2007 : {
2008 128 : isVRTOutput = EQUAL(
2009 : CPLGetExtensionSafe(outputFileName.c_str()).c_str(), "vrt");
2010 : }
2011 208 : if (isVRTOutput && !outputFileName.empty() && m_steps.size() > 3)
2012 : {
2013 1 : ReportError(
2014 : CE_Failure, CPLE_NotSupported,
2015 : "VRT output is not supported when there are more than 3 "
2016 : "steps. Consider using the GDALG driver (files with "
2017 : ".gdalg.json extension)");
2018 1 : return RunStepState::ERROR;
2019 : }
2020 207 : if (isVRTOutput)
2021 : {
2022 31 : for (const auto &step : m_steps)
2023 : {
2024 29 : if (!step->m_outputVRTCompatible)
2025 : {
2026 12 : step->ReportError(
2027 : CE_Failure, CPLE_NotSupported,
2028 : "VRT output is not supported. Consider using the "
2029 : "GDALG driver instead (files with .gdalg.json "
2030 : "extension)");
2031 12 : return RunStepState::ERROR;
2032 : }
2033 : }
2034 : }
2035 : }
2036 : }
2037 :
2038 394 : if (m_executionForStreamOutput &&
2039 44 : !CPLTestBool(
2040 : CPLGetConfigOption("GDAL_ALGORITHM_ALLOW_WRITES_IN_STREAM", "NO")))
2041 : {
2042 : // For security reasons, to avoid that reading a .gdalg.json file writes
2043 : // a file on the file system.
2044 99 : for (const auto &step : m_steps)
2045 : {
2046 62 : if (step->GetName() == GDALRasterWriteAlgorithm::NAME)
2047 : {
2048 3 : if (!EQUAL(step->m_format.c_str(), "stream"))
2049 : {
2050 2 : ReportError(CE_Failure, CPLE_AppDefined,
2051 : "in streamed execution, --format "
2052 : "stream should be used");
2053 5 : return RunStepState::ERROR;
2054 : }
2055 : }
2056 59 : else if (step->GeneratesFilesFromUserInput())
2057 : {
2058 3 : ReportError(CE_Failure, CPLE_AppDefined,
2059 : "Step '%s' not allowed in stream execution, unless "
2060 : "the GDAL_ALGORITHM_ALLOW_WRITES_IN_STREAM "
2061 : "configuration option is set.",
2062 3 : step->GetName().c_str());
2063 3 : return RunStepState::ERROR;
2064 : }
2065 : }
2066 : }
2067 :
2068 345 : return RunStepState::GO_ON;
2069 : }
2070 :
2071 : /************************************************************************/
2072 : /* RunStepDealWithMultiProcessing() */
2073 : /************************************************************************/
2074 :
2075 : GDALAbstractPipelineAlgorithm::RunStepState
2076 345 : GDALAbstractPipelineAlgorithm::RunStepDealWithMultiProcessing(
2077 : GDALPipelineStepRunContext &ctxt)
2078 : {
2079 : // Because of multiprocessing in gdal raster tile, make sure that all
2080 : // steps before it are either materialized or serialized in a .gdal.json file
2081 289 : if (m_steps.size() >= 2 && m_steps.back()->SupportsInputMultiThreading() &&
2082 7 : m_steps.back()
2083 7 : ->GetArg(GDAL_ARG_NAME_NUM_THREADS_INT_HIDDEN)
2084 644 : ->Get<int>() > 1 &&
2085 7 : !(m_steps.size() == 2 &&
2086 3 : m_steps[0]->GetName() == GDALRasterReadAlgorithm::NAME))
2087 : {
2088 6 : if (m_steps[m_steps.size() - 2]->GetName() ==
2089 : GDALMaterializeRasterAlgorithm::NAME)
2090 : {
2091 : // If the step immediately before the last one is materialize,
2092 : // then make sure the file it generates can be re-opened
2093 3 : auto poArg = m_steps[m_steps.size() - 2]->GetArg(
2094 : GDALMaterializeRasterAlgorithm::
2095 : ARG_NAME_REOPEN_AND_DO_NOT_EARLY_DELETE);
2096 3 : CPLAssert(poArg);
2097 3 : poArg->Set(true);
2098 : }
2099 : else
2100 : {
2101 3 : for (size_t i = m_steps.size() - 2; i > 0;)
2102 : {
2103 1 : --i;
2104 1 : if (m_steps[i]->GetName() ==
2105 : GDALMaterializeRasterAlgorithm::NAME)
2106 : {
2107 1 : auto poArg = m_steps[i]->GetArg(GDAL_ARG_NAME_OUTPUT);
2108 1 : CPLAssert(poArg);
2109 1 : if (poArg->IsExplicitlySet())
2110 : {
2111 : // We could potentially support that scenario but that
2112 : // would require executing the pipeline up to that
2113 : // step, and serializing the rest to GDALG
2114 1 : ReportError(
2115 : CE_Failure, CPLE_AppDefined,
2116 : "Cannot execute this pipeline in parallel mode due "
2117 : "to the presence of a materialize step that has a "
2118 : "'output' argument and is not immediately before "
2119 : "the last step. "
2120 : "Move/create a materialize step immediately before "
2121 : "the last step, or add '-j 1' to the last step "
2122 : "'%s'",
2123 1 : m_steps.back()->GetName().c_str());
2124 1 : return RunStepState::ERROR;
2125 : }
2126 : }
2127 : }
2128 :
2129 2 : bool ret = false;
2130 2 : auto poSrcDS = m_inputDataset.size() == 1
2131 2 : ? m_inputDataset[0].GetDatasetRef()
2132 2 : : nullptr;
2133 2 : if (poSrcDS)
2134 : {
2135 1 : auto poSrcDriver = poSrcDS->GetDriver();
2136 1 : if (!poSrcDriver || EQUAL(poSrcDriver->GetDescription(), "MEM"))
2137 : {
2138 1 : ReportError(
2139 : CE_Failure, CPLE_AppDefined,
2140 : "Cannot execute this pipeline in parallel mode due to "
2141 : "input dataset of last step being a non-materialized "
2142 : "dataset. "
2143 : "Materialize it first, or add '-j 1' to the last step "
2144 : "'%s'",
2145 1 : m_steps.back()->GetName().c_str());
2146 1 : return RunStepState::ERROR;
2147 : }
2148 : }
2149 2 : std::string outString;
2150 1 : if (SaveGDALGIntoFileOrString(std::string(), outString))
2151 : {
2152 1 : const char *const apszAllowedDrivers[] = {"GDALG", nullptr};
2153 1 : auto poCurDS = GDALDataset::Open(
2154 : outString.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
2155 : apszAllowedDrivers);
2156 1 : if (poCurDS)
2157 : {
2158 1 : auto &lastAlg = m_steps.back();
2159 1 : lastAlg->m_inputDataset.clear();
2160 1 : lastAlg->m_inputDataset.resize(1);
2161 1 : lastAlg->m_inputDataset[0].Set(poCurDS);
2162 1 : lastAlg->m_inputDataset[0].SetDatasetOpenedByAlgorithm();
2163 1 : poCurDS->Release();
2164 1 : ret = lastAlg->RunStep(ctxt);
2165 1 : lastAlg->m_inputDataset[0].Close();
2166 : }
2167 : }
2168 : else
2169 : {
2170 0 : ReportError(
2171 : CE_Failure, CPLE_AppDefined,
2172 : "Cannot execute this pipeline in parallel mode due to "
2173 : "an unexpected error. "
2174 : "Trying adding a materialize step before the last step "
2175 : "'%s', or add '-j 1' to the last step.",
2176 0 : m_steps.back()->GetName().c_str());
2177 0 : return RunStepState::ERROR;
2178 : }
2179 1 : return ret ? RunStepState::PROCESSED : RunStepState::ERROR;
2180 : }
2181 : }
2182 :
2183 342 : return RunStepState::GO_ON;
2184 : }
2185 :
2186 : /************************************************************************/
2187 : /* RunStepDealWithStepUnknownInputType() */
2188 : /************************************************************************/
2189 :
2190 21 : bool GDALAbstractPipelineAlgorithm::RunStepDealWithStepUnknownInputType(
2191 : size_t i, int nCurDatasetType)
2192 : {
2193 : // We go here if there was a step such as "external" where at
2194 : // ParseCommandLineArguments() time we could not determine its
2195 : // type of output dataset. Now we must check for steps afterwards
2196 : // such as "write" or "reproject" that exist both as separate raster
2197 : // and vector commands if the one we initially picked is appropriate.
2198 : // If not, then switch to the other type.
2199 :
2200 : // GetInputDatasetType() is to deal with pipelines like:
2201 : // gdal pipeline read input_vector !
2202 : // external --command "cp <INPUT> <OUTPUT>" !
2203 : // clip --input raster_dataset --like _PIPE_ !
2204 : // write output_raster
2205 :
2206 21 : auto &step = m_steps[i];
2207 21 : const int nThisDatasetType = GetInputDatasetType(step.get());
2208 21 : const int nThisStepType =
2209 21 : nThisDatasetType ? nThisDatasetType : nCurDatasetType;
2210 21 : if (step->GetInputType() != 0 && step->GetInputType() != nThisStepType)
2211 : {
2212 : auto newAlg = GetStepAlg(
2213 6 : step->GetName() +
2214 6 : (nThisStepType == GDAL_OF_RASTER ? RASTER_SUFFIX : VECTOR_SUFFIX));
2215 6 : if (newAlg)
2216 : {
2217 : const bool maybeWriteStep =
2218 9 : (i == m_steps.size() - 1 &&
2219 3 : m_eLastStepAsWrite != StepConstraint::CAN_NOT_BE);
2220 6 : if (!CopyStepAlgorithmFromAnother(newAlg.get(), step.get(),
2221 : maybeWriteStep))
2222 0 : return false;
2223 6 : newAlg->m_inputDatasetCanBeOmitted = true;
2224 :
2225 6 : step = std::move(newAlg);
2226 : }
2227 : }
2228 :
2229 21 : return step->ValidateArguments();
2230 : }
2231 :
2232 : /************************************************************************/
2233 : /* CheckStepHasNoInputDatasetAlreadySet() */
2234 : /************************************************************************/
2235 :
2236 432 : bool GDALAbstractPipelineAlgorithm::CheckStepHasNoInputDatasetAlreadySet(
2237 : size_t i, GDALDataset *poCurDS)
2238 : {
2239 432 : auto &step = *(m_steps[i]);
2240 432 : bool prevStepOutputSetToThisStep = false;
2241 6429 : for (auto &arg : step.GetArgs())
2242 : {
2243 11684 : if (!arg->IsOutput() && (arg->GetType() == GAAT_DATASET ||
2244 5687 : arg->GetType() == GAAT_DATASET_LIST))
2245 : {
2246 512 : if (arg->GetType() == GAAT_DATASET)
2247 : {
2248 60 : if ((arg->GetName() == GDAL_ARG_NAME_INPUT &&
2249 120 : !arg->IsExplicitlySet()) ||
2250 60 : arg->Get<GDALArgDatasetValue>().GetName() ==
2251 : GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE)
2252 : {
2253 14 : auto &val = arg->Get<GDALArgDatasetValue>();
2254 14 : if (val.GetDatasetRef())
2255 : {
2256 : // Shouldn't happen
2257 0 : ReportError(CE_Failure, CPLE_AppDefined,
2258 : "Step nr %d (%s) has already an "
2259 : "input dataset for argument %s",
2260 0 : static_cast<int>(i), step.GetName().c_str(),
2261 0 : arg->GetName().c_str());
2262 0 : return false;
2263 : }
2264 14 : prevStepOutputSetToThisStep = true;
2265 14 : val.Set(poCurDS);
2266 14 : arg->NotifyValueSet();
2267 : }
2268 : }
2269 : else
2270 : {
2271 452 : CPLAssert(arg->GetType() == GAAT_DATASET_LIST);
2272 452 : auto &val = arg->Get<std::vector<GDALArgDatasetValue>>();
2273 452 : if ((arg->GetName() == GDAL_ARG_NAME_INPUT &&
2274 518 : !arg->IsExplicitlySet()) ||
2275 66 : (val.size() == 1 &&
2276 30 : val[0].GetName() ==
2277 : GDAL_DATASET_PIPELINE_PLACEHOLDER_VALUE))
2278 : {
2279 419 : if (val.size() == 1 && val[0].GetDatasetRef())
2280 : {
2281 : // Shouldn't happen
2282 0 : ReportError(CE_Failure, CPLE_AppDefined,
2283 : "Step nr %d (%s) has already an "
2284 : "input dataset for argument %s",
2285 0 : static_cast<int>(i), step.GetName().c_str(),
2286 0 : arg->GetName().c_str());
2287 0 : return false;
2288 : }
2289 419 : prevStepOutputSetToThisStep = true;
2290 419 : val.clear();
2291 419 : val.resize(1);
2292 419 : val[0].Set(poCurDS);
2293 419 : arg->NotifyValueSet();
2294 : }
2295 : }
2296 : }
2297 : }
2298 432 : if (!prevStepOutputSetToThisStep)
2299 : {
2300 1 : ReportError(CE_Failure, CPLE_AppDefined,
2301 : "Step nr %d (%s) does not use input dataset from "
2302 : "previous step",
2303 1 : static_cast<int>(i), step.GetName().c_str());
2304 1 : return false;
2305 : }
2306 :
2307 431 : return true;
2308 : }
2309 :
2310 : /************************************************************************/
2311 : /* GDALAbstractPipelineAlgorithm::RunStep() */
2312 : /************************************************************************/
2313 :
2314 424 : bool GDALAbstractPipelineAlgorithm::RunStep(GDALPipelineStepRunContext &ctxt)
2315 : {
2316 424 : if (m_stepOnWhichHelpIsRequested)
2317 : {
2318 6 : printf(
2319 : "%s",
2320 12 : m_stepOnWhichHelpIsRequested->GetUsageForCLI(false).c_str()); /*ok*/
2321 6 : return true;
2322 : }
2323 :
2324 418 : if (m_steps.empty())
2325 : {
2326 : // If invoked programmatically, not from the command line.
2327 :
2328 261 : if (m_pipeline.empty())
2329 : {
2330 12 : ReportError(CE_Failure, CPLE_AppDefined,
2331 : "'pipeline' argument not set");
2332 43 : return false;
2333 : }
2334 :
2335 249 : const CPLStringList aosTokens(CSLTokenizeString(m_pipeline.c_str()));
2336 249 : if (!ParseCommandLineArguments(aosTokens))
2337 31 : return false;
2338 : }
2339 :
2340 375 : switch (RunStepDealWithGDALGJson())
2341 : {
2342 11 : case RunStepState::PROCESSED:
2343 11 : return true;
2344 19 : case RunStepState::ERROR:
2345 19 : return false;
2346 345 : case RunStepState::GO_ON:
2347 345 : break;
2348 : }
2349 345 : switch (RunStepDealWithMultiProcessing(ctxt))
2350 : {
2351 1 : case RunStepState::PROCESSED:
2352 1 : return true;
2353 2 : case RunStepState::ERROR:
2354 2 : return false;
2355 342 : case RunStepState::GO_ON:
2356 342 : break;
2357 : }
2358 :
2359 342 : int countPipelinesWithProgress = 0;
2360 802 : for (size_t i = (m_bExpectReadStep ? 0 : 1); i < m_steps.size(); ++i)
2361 : {
2362 : const bool bCanHandleNextStep =
2363 769 : i < m_steps.size() - 1 &&
2364 309 : !m_steps[i]->CanHandleNextStep(m_steps[i + 1].get());
2365 769 : if (bCanHandleNextStep &&
2366 309 : !m_steps[i + 1]->IsNativelyStreamingCompatible())
2367 182 : ++countPipelinesWithProgress;
2368 278 : else if (!m_steps[i]->IsNativelyStreamingCompatible())
2369 107 : ++countPipelinesWithProgress;
2370 460 : if (bCanHandleNextStep)
2371 309 : ++i;
2372 : }
2373 342 : if (countPipelinesWithProgress == 0)
2374 114 : countPipelinesWithProgress = 1;
2375 :
2376 342 : bool ret = true;
2377 342 : GDALDataset *poCurDS = nullptr;
2378 342 : int iCurStepWithProgress = 0;
2379 :
2380 342 : if (!m_bExpectReadStep)
2381 : {
2382 17 : CPLAssert(m_inputDataset.size() == 1);
2383 17 : poCurDS = m_inputDataset[0].GetDatasetRef();
2384 17 : CPLAssert(poCurDS);
2385 : }
2386 :
2387 342 : GDALProgressFunc pfnProgress = ctxt.m_pfnProgress;
2388 342 : void *pProgressData = ctxt.m_pProgressData;
2389 342 : if (IsCalledFromCommandLine() && HasOutputString())
2390 : {
2391 6 : pfnProgress = nullptr;
2392 6 : pProgressData = nullptr;
2393 : }
2394 :
2395 342 : int nCurDatasetType = poCurDS ? GetDatasetType(poCurDS) : 0;
2396 :
2397 1058 : for (size_t i = 0; i < m_steps.size(); ++i)
2398 : {
2399 757 : auto &step = m_steps[i];
2400 :
2401 415 : if (i > 0 && m_nFirstStepWithUnknownInputType >= 0 &&
2402 45 : i >= static_cast<size_t>(m_nFirstStepWithUnknownInputType) &&
2403 1172 : nCurDatasetType != 0 && GetStepAlg(step->GetName()) == nullptr)
2404 : {
2405 21 : if (!RunStepDealWithStepUnknownInputType(i, nCurDatasetType))
2406 1 : return false;
2407 : }
2408 :
2409 757 : if (i > 0 || poCurDS)
2410 : {
2411 432 : if (!CheckStepHasNoInputDatasetAlreadySet(i, poCurDS))
2412 1 : return false;
2413 : }
2414 :
2415 758 : if (i + 1 < m_steps.size() && step->m_outputDataset.GetDatasetRef() &&
2416 2 : !step->OutputDatasetAllowedBeforeRunningStep())
2417 : {
2418 : // Shouldn't happen
2419 0 : ReportError(CE_Failure, CPLE_AppDefined,
2420 : "Step nr %d (%s) has already an output dataset",
2421 0 : static_cast<int>(i), step->GetName().c_str());
2422 0 : return false;
2423 : }
2424 :
2425 : const bool bCanHandleNextStep =
2426 1200 : i < m_steps.size() - 1 &&
2427 444 : step->CanHandleNextStep(m_steps[i + 1].get());
2428 :
2429 : std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> pScaledData(
2430 756 : nullptr, GDALDestroyScaledProgress);
2431 756 : GDALPipelineStepRunContext stepCtxt;
2432 766 : if ((bCanHandleNextStep &&
2433 1512 : m_steps[i + 1]->IsNativelyStreamingCompatible()) ||
2434 756 : !step->IsNativelyStreamingCompatible())
2435 : {
2436 279 : pScaledData.reset(GDALCreateScaledProgress(
2437 : iCurStepWithProgress /
2438 279 : static_cast<double>(countPipelinesWithProgress),
2439 279 : (iCurStepWithProgress + 1) /
2440 279 : static_cast<double>(countPipelinesWithProgress),
2441 : pfnProgress, pProgressData));
2442 279 : ++iCurStepWithProgress;
2443 279 : stepCtxt.m_pfnProgress = pScaledData ? GDALScaledProgress : nullptr;
2444 279 : stepCtxt.m_pProgressData = pScaledData.get();
2445 : }
2446 756 : if (bCanHandleNextStep)
2447 : {
2448 10 : stepCtxt.m_poNextUsableStep = m_steps[i + 1].get();
2449 : }
2450 773 : if (i + 1 == m_steps.size() && m_stdout &&
2451 773 : step->GetArg(GDAL_ARG_NAME_STDOUT) != nullptr)
2452 : {
2453 4 : step->m_stdout = true;
2454 : }
2455 756 : step->m_inputDatasetCanBeOmitted = false;
2456 756 : step->m_quiet = m_quiet;
2457 756 : if (!step->ValidateArguments() || !step->RunStep(stepCtxt))
2458 : {
2459 40 : ret = false;
2460 40 : break;
2461 : }
2462 716 : poCurDS = step->m_outputDataset.GetDatasetRef();
2463 716 : nCurDatasetType = 0;
2464 716 : if (poCurDS)
2465 : {
2466 690 : nCurDatasetType = GetDatasetType(poCurDS);
2467 : }
2468 64 : else if (!(i + 1 == m_steps.size() &&
2469 26 : (!step->m_output.empty() ||
2470 38 : step->GetArg(GDAL_ARG_NAME_STDOUT) != nullptr ||
2471 7 : step->GetOutputType() == 0)))
2472 : {
2473 0 : ReportError(CE_Failure, CPLE_AppDefined,
2474 : "Step nr %d (%s) failed to produce an output dataset",
2475 0 : static_cast<int>(i), step->GetName().c_str());
2476 0 : return false;
2477 : }
2478 :
2479 716 : m_output += step->GetOutputString();
2480 :
2481 716 : if (bCanHandleNextStep)
2482 : {
2483 10 : ++i;
2484 : }
2485 : }
2486 :
2487 341 : if (pfnProgress && m_output.empty())
2488 16 : pfnProgress(1.0, "", pProgressData);
2489 :
2490 341 : if (!m_output.empty())
2491 : {
2492 17 : auto outputStringArg = GetArg(GDAL_ARG_NAME_OUTPUT_STRING);
2493 17 : if (outputStringArg && outputStringArg->GetType() == GAAT_STRING)
2494 17 : outputStringArg->Set(m_output);
2495 : }
2496 :
2497 341 : if (ret && poCurDS && !m_outputDataset.GetDatasetRef())
2498 : {
2499 274 : m_outputDataset.Set(poCurDS);
2500 : }
2501 :
2502 341 : return ret;
2503 : }
2504 :
2505 : /************************************************************************/
2506 : /* GDALAbstractPipelineAlgorithm::HasOutputString() */
2507 : /************************************************************************/
2508 :
2509 35 : bool GDALAbstractPipelineAlgorithm::HasOutputString() const
2510 : {
2511 103 : for (const auto &step : m_steps)
2512 : {
2513 74 : if (step->HasOutputString())
2514 6 : return true;
2515 : }
2516 29 : return false;
2517 : }
2518 :
2519 : /************************************************************************/
2520 : /* GDALAbstractPipelineAlgorithm::Finalize() */
2521 : /************************************************************************/
2522 :
2523 236 : bool GDALAbstractPipelineAlgorithm::Finalize()
2524 : {
2525 236 : bool ret = GDALPipelineStepAlgorithm::Finalize();
2526 : // Finalize steps in reverse order, typically to make sure later steps
2527 : // have dropped their reference on datasets passed by previous ones.
2528 : // This helps for example for the "external" step that needs to delete
2529 : // temporary files.
2530 751 : for (auto iter = m_steps.rbegin(); iter != m_steps.rend(); ++iter)
2531 : {
2532 515 : ret = (*iter)->Finalize() && ret;
2533 : }
2534 236 : return ret;
2535 : }
2536 :
2537 : /************************************************************************/
2538 : /* GDALAbstractPipelineAlgorithm::GetUsageAsJSON() */
2539 : /************************************************************************/
2540 :
2541 9 : std::string GDALAbstractPipelineAlgorithm::GetUsageAsJSON() const
2542 : {
2543 18 : CPLJSONDocument oDoc;
2544 9 : CPL_IGNORE_RET_VAL(oDoc.LoadMemory(GDALAlgorithm::GetUsageAsJSON()));
2545 :
2546 18 : CPLJSONArray jPipelineSteps;
2547 378 : for (const std::string &name : GetStepRegistry().GetNames())
2548 : {
2549 738 : auto alg = GetStepAlg(name);
2550 369 : if (!alg->IsHidden())
2551 : {
2552 369 : CPLJSONDocument oStepDoc;
2553 369 : CPL_IGNORE_RET_VAL(oStepDoc.LoadMemory(alg->GetUsageAsJSON()));
2554 369 : jPipelineSteps.Add(oStepDoc.GetRoot());
2555 : }
2556 : }
2557 9 : oDoc.GetRoot().Add("pipeline_algorithms", jPipelineSteps);
2558 :
2559 18 : return oDoc.SaveAsString();
2560 : }
2561 :
2562 : //! @endcond
|