Coverage Report

Created: 2026-02-14 06:52

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