LCOV - code coverage report
Current view: top level - apps - gdalalg_abstract_pipeline.h (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 123 140 87.9 %
Date: 2025-03-28 11:40:40 Functions: 14 18 77.8 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "raster/vector pipeline" subcommand
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #ifndef GDALALG_ABSTRACT_PIPELINE_INCLUDED
      14             : #define GDALALG_ABSTRACT_PIPELINE_INCLUDED
      15             : 
      16             : //! @cond Doxygen_Suppress
      17             : 
      18             : #include "cpl_conv.h"
      19             : #include "cpl_json.h"
      20             : #include "gdalalgorithm.h"
      21             : 
      22             : #include <algorithm>
      23             : 
      24             : template <class StepAlgorithm>
      25             : class GDALAbstractPipelineAlgorithm CPL_NON_FINAL : public StepAlgorithm
      26             : {
      27             :   public:
      28             :     std::vector<std::string> GetAutoComplete(std::vector<std::string> &args,
      29             :                                              bool /* showAllOptions*/) override;
      30             : 
      31             :     bool Finalize() override;
      32             : 
      33             :     std::string GetUsageAsJSON() const override;
      34             : 
      35             :     /* cppcheck-suppress functionStatic */
      36           0 :     void SetDataset(GDALDataset *)
      37             :     {
      38           0 :     }
      39             : 
      40             :   protected:
      41         145 :     GDALAbstractPipelineAlgorithm(const std::string &name,
      42             :                                   const std::string &description,
      43             :                                   const std::string &helpURL,
      44             :                                   bool standaloneStep)
      45         145 :         : StepAlgorithm(name, description, helpURL, standaloneStep)
      46             :     {
      47         145 :     }
      48             : 
      49         145 :     ~GDALAbstractPipelineAlgorithm() override
      50             :     {
      51             :         // Destroy steps in the reverse order they have been constructed,
      52             :         // as a step can create object that depends on the validity of
      53             :         // objects of previous steps, and while cleaning them it needs those
      54             :         // prior objects to be still alive.
      55             :         // Typically for "gdal vector pipeline read ... ! sql ..."
      56         311 :         for (auto it = std::rbegin(m_steps); it != std::rend(m_steps); it++)
      57             :         {
      58         166 :             it->reset();
      59             :         }
      60         290 :     }
      61             : 
      62             :     virtual GDALArgDatasetValue &GetOutputDataset() = 0;
      63             : 
      64             :     std::string m_pipeline{};
      65             : 
      66             :     std::unique_ptr<StepAlgorithm> GetStepAlg(const std::string &name) const;
      67             : 
      68             :     GDALAlgorithmRegistry m_stepRegistry{};
      69             :     std::vector<std::unique_ptr<StepAlgorithm>> m_steps{};
      70             : 
      71             :   private:
      72             :     bool RunStep(GDALProgressFunc pfnProgress, void *pProgressData) override;
      73             : };
      74             : 
      75             : /************************************************************************/
      76             : /*              GDALAbstractPipelineAlgorithm::GetStepAlg()             */
      77             : /************************************************************************/
      78             : 
      79             : template <class StepAlgorithm>
      80             : std::unique_ptr<StepAlgorithm>
      81         356 : GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetStepAlg(
      82             :     const std::string &name) const
      83             : {
      84         712 :     auto alg = m_stepRegistry.Instantiate(name);
      85             :     return std::unique_ptr<StepAlgorithm>(
      86         712 :         cpl::down_cast<StepAlgorithm *>(alg.release()));
      87             : }
      88             : 
      89             : /************************************************************************/
      90             : /*         GDALAbstractPipelineAlgorithm::GetAutoComplete()             */
      91             : /************************************************************************/
      92             : 
      93             : template <class StepAlgorithm>
      94             : std::vector<std::string>
      95          17 : GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetAutoComplete(
      96             :     std::vector<std::string> &args, bool /* showAllOptions*/)
      97             : {
      98          17 :     std::vector<std::string> ret;
      99          17 :     if (args.size() <= 1)
     100             :     {
     101           6 :         if (args.empty() || args.front() != "read")
     102           4 :             ret.push_back("read");
     103             :     }
     104          23 :     else if (args.back() == "!" ||
     105          23 :              (args[args.size() - 2] == "!" && !GetStepAlg(args.back())))
     106             :     {
     107          42 :         for (const std::string &name : m_stepRegistry.GetNames())
     108             :         {
     109          38 :             if (name != "read")
     110             :             {
     111          34 :                 ret.push_back(name);
     112             :             }
     113             :         }
     114             :     }
     115             :     else
     116             :     {
     117          14 :         std::string lastStep = "read";
     118          14 :         std::vector<std::string> lastArgs;
     119          24 :         for (size_t i = 1; i < args.size(); ++i)
     120             :         {
     121          17 :             lastArgs.push_back(args[i]);
     122          17 :             if (i + 1 < args.size() && args[i] == "!")
     123             :             {
     124           6 :                 ++i;
     125           6 :                 lastArgs.clear();
     126           6 :                 lastStep = args[i];
     127             :             }
     128             :         }
     129             : 
     130          14 :         auto curAlg = GetStepAlg(lastStep);
     131           7 :         if (curAlg)
     132             :         {
     133           7 :             ret =
     134           7 :                 curAlg->GetAutoComplete(lastArgs, /* showAllOptions = */ false);
     135             :         }
     136             :     }
     137          17 :     return ret;
     138             : }
     139             : 
     140             : /************************************************************************/
     141             : /*              GDALAbstractPipelineAlgorithm::RunStep()                */
     142             : /************************************************************************/
     143             : 
     144             : template <class StepAlgorithm>
     145          75 : bool GDALAbstractPipelineAlgorithm<StepAlgorithm>::RunStep(
     146             :     GDALProgressFunc pfnProgress, void *pProgressData)
     147             : {
     148          75 :     if (m_steps.empty())
     149             :     {
     150             :         // If invoked programmatically, not from the command line.
     151             : 
     152          17 :         if (m_pipeline.empty())
     153             :         {
     154           2 :             StepAlgorithm::ReportError(CE_Failure, CPLE_AppDefined,
     155             :                                        "'pipeline' argument not set");
     156           4 :             return false;
     157             :         }
     158             : 
     159          15 :         const CPLStringList aosTokens(CSLTokenizeString(m_pipeline.c_str()));
     160          15 :         if (!this->ParseCommandLineArguments(aosTokens))
     161           2 :             return false;
     162             :     }
     163             : 
     164             :     // Handle output to GDALG file
     165          71 :     if (!m_steps.empty() && m_steps.back()->GetName() == "write")
     166             :     {
     167          71 :         if (m_steps.back()->IsGDALGOutput())
     168             :         {
     169           3 :             const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
     170           3 :             const auto &filename =
     171             :                 outputArg->GDALAlgorithmArg::template Get<GDALArgDatasetValue>()
     172           3 :                     .GetName();
     173             :             VSIStatBufL sStat;
     174           3 :             if (VSIStatL(filename.c_str(), &sStat) == 0)
     175             :             {
     176           0 :                 const auto overwriteArg =
     177           0 :                     m_steps.back()->GetArg(GDAL_ARG_NAME_OVERWRITE);
     178           0 :                 if (overwriteArg && overwriteArg->GetType() == GAAT_BOOLEAN)
     179             :                 {
     180           0 :                     if (!overwriteArg->GDALAlgorithmArg::template Get<bool>())
     181             :                     {
     182           0 :                         CPLError(CE_Failure, CPLE_AppDefined,
     183             :                                  "File '%s' already exists. Specify the "
     184             :                                  "--overwrite option to overwrite it.",
     185             :                                  filename.c_str());
     186           0 :                         return false;
     187             :                     }
     188             :                 }
     189             :             }
     190             : 
     191           6 :             std::string osCommandLine;
     192             : 
     193          12 :             for (const auto &path : GDALAlgorithm::m_callPath)
     194             :             {
     195           9 :                 if (!osCommandLine.empty())
     196           6 :                     osCommandLine += ' ';
     197           9 :                 osCommandLine += path;
     198             :             }
     199             : 
     200             :             // Do not include the last step
     201           9 :             for (size_t i = 0; i + 1 < m_steps.size(); ++i)
     202             :             {
     203           6 :                 const auto &step = m_steps[i];
     204           6 :                 if (i > 0)
     205           3 :                     osCommandLine += " !";
     206          13 :                 for (const auto &path : step->GDALAlgorithm::m_callPath)
     207             :                 {
     208           7 :                     if (!osCommandLine.empty())
     209           7 :                         osCommandLine += ' ';
     210           7 :                     osCommandLine += path;
     211             :                 }
     212             : 
     213          84 :                 for (const auto &arg : step->GetArgs())
     214             :                 {
     215          78 :                     if (arg->IsExplicitlySet())
     216             :                     {
     217           6 :                         osCommandLine += ' ';
     218           6 :                         std::string strArg;
     219           6 :                         if (!arg->Serialize(strArg))
     220             :                         {
     221           0 :                             CPLError(CE_Failure, CPLE_AppDefined,
     222             :                                      "Cannot serialize argument %s",
     223           0 :                                      arg->GetName().c_str());
     224           0 :                             return false;
     225             :                         }
     226           6 :                         osCommandLine += strArg;
     227             :                     }
     228             :                 }
     229             :             }
     230             : 
     231           3 :             CPLJSONDocument oDoc;
     232           3 :             oDoc.GetRoot().Add("type", "gdal_streamed_alg");
     233           3 :             oDoc.GetRoot().Add("command_line", osCommandLine);
     234             : 
     235           3 :             return oDoc.Save(filename);
     236             :         }
     237             : 
     238          68 :         const auto outputFormatArg =
     239          68 :             m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT_FORMAT);
     240          68 :         const auto outputArg = m_steps.back()->GetArg(GDAL_ARG_NAME_OUTPUT);
     241          68 :         if (outputArg && outputArg->GetType() == GAAT_DATASET &&
     242             :             outputArg->IsExplicitlySet())
     243             :         {
     244          68 :             const auto &outputFile =
     245             :                 outputArg
     246             :                     ->GDALAlgorithmArg::template Get<GDALArgDatasetValue>();
     247             :             bool isVRTOutput;
     248          68 :             if (outputFormatArg && outputFormatArg->GetType() == GAAT_STRING &&
     249             :                 outputFormatArg->IsExplicitlySet())
     250             :             {
     251          11 :                 const auto &val =
     252             :                     outputFormatArg
     253             :                         ->GDALAlgorithmArg::template Get<std::string>();
     254          11 :                 isVRTOutput = EQUAL(val.c_str(), "vrt");
     255             :             }
     256             :             else
     257             :             {
     258          57 :                 isVRTOutput = EQUAL(
     259             :                     CPLGetExtensionSafe(outputFile.GetName().c_str()).c_str(),
     260             :                     "vrt");
     261             :             }
     262          69 :             if (isVRTOutput && !outputFile.GetName().empty() &&
     263           1 :                 m_steps.size() > 3)
     264             :             {
     265           1 :                 StepAlgorithm::ReportError(
     266             :                     CE_Failure, CPLE_NotSupported,
     267             :                     "VRT output is not supported when there are more than 3 "
     268             :                     "steps. Consider using the GDALG driver (files with "
     269             :                     ".gdalg.json extension)");
     270           1 :                 return false;
     271             :             }
     272             :         }
     273             :     }
     274             : 
     275          67 :     if (GDALAlgorithm::m_executionForStreamOutput)
     276             :     {
     277             :         // For security reasons, to avoid that reading a .gdalg.json file writes
     278             :         // a file on the file system.
     279          22 :         for (const auto &step : m_steps)
     280             :         {
     281          24 :             if (step->GetName() == "write" &&
     282           8 :                 !EQUAL(step->m_format.c_str(), "stream"))
     283             :             {
     284           2 :                 StepAlgorithm::ReportError(CE_Failure, CPLE_AppDefined,
     285             :                                            "in streamed execution, --format "
     286             :                                            "stream should be used");
     287           2 :                 return false;
     288             :             }
     289             :         }
     290             :     }
     291             : 
     292          65 :     GDALDataset *poCurDS = nullptr;
     293         202 :     for (size_t i = 0; i < m_steps.size(); ++i)
     294             :     {
     295         146 :         auto &step = m_steps[i];
     296         146 :         if (i > 0)
     297             :         {
     298          81 :             if (step->m_inputDataset.GetDatasetRef())
     299             :             {
     300             :                 // Shouldn't happen
     301           0 :                 StepAlgorithm::ReportError(
     302             :                     CE_Failure, CPLE_AppDefined,
     303             :                     "Step nr %d (%s) has already an input dataset",
     304           0 :                     static_cast<int>(i), step->GetName().c_str());
     305           0 :                 return false;
     306             :             }
     307          81 :             step->m_inputDataset.Set(poCurDS);
     308             :         }
     309         146 :         if (i + 1 < m_steps.size() && step->m_outputDataset.GetDatasetRef())
     310             :         {
     311             :             // Shouldn't happen
     312           2 :             StepAlgorithm::ReportError(
     313             :                 CE_Failure, CPLE_AppDefined,
     314             :                 "Step nr %d (%s) has already an output dataset",
     315           2 :                 static_cast<int>(i), step->GetName().c_str());
     316           2 :             return false;
     317             :         }
     318         288 :         if (!step->Run(i < m_steps.size() - 1 ? nullptr : pfnProgress,
     319         144 :                        i < m_steps.size() - 1 ? nullptr : pProgressData))
     320             :         {
     321           7 :             return false;
     322             :         }
     323         137 :         poCurDS = step->m_outputDataset.GetDatasetRef();
     324         137 :         if (!poCurDS)
     325             :         {
     326           0 :             StepAlgorithm::ReportError(
     327             :                 CE_Failure, CPLE_AppDefined,
     328             :                 "Step nr %d (%s) failed to produce an output dataset",
     329           0 :                 static_cast<int>(i), step->GetName().c_str());
     330           0 :             return false;
     331             :         }
     332             :     }
     333             : 
     334          56 :     if (!GetOutputDataset().GetDatasetRef())
     335             :     {
     336          56 :         GetOutputDataset().Set(poCurDS);
     337             :     }
     338             : 
     339          56 :     return true;
     340             : }
     341             : 
     342             : /************************************************************************/
     343             : /*               GDALAbstractPipelineAlgorithm::Finalize()              */
     344             : /************************************************************************/
     345             : 
     346             : template <class StepAlgorithm>
     347          56 : bool GDALAbstractPipelineAlgorithm<StepAlgorithm>::Finalize()
     348             : {
     349          56 :     bool ret = GDALAlgorithm::Finalize();
     350         173 :     for (auto &step : m_steps)
     351             :     {
     352         117 :         ret = step->Finalize() && ret;
     353             :     }
     354          56 :     return ret;
     355             : }
     356             : 
     357             : /************************************************************************/
     358             : /*             GDALAbstractPipelineAlgorithm::GetUsageAsJSON()          */
     359             : /************************************************************************/
     360             : 
     361             : template <class StepAlgorithm>
     362           7 : std::string GDALAbstractPipelineAlgorithm<StepAlgorithm>::GetUsageAsJSON() const
     363             : {
     364          14 :     CPLJSONDocument oDoc;
     365           7 :     CPL_IGNORE_RET_VAL(oDoc.LoadMemory(GDALAlgorithm::GetUsageAsJSON()));
     366             : 
     367          14 :     CPLJSONArray jPipelineSteps;
     368          74 :     for (const std::string &name : m_stepRegistry.GetNames())
     369             :     {
     370         134 :         auto alg = GetStepAlg(name);
     371          67 :         CPLJSONDocument oStepDoc;
     372          67 :         CPL_IGNORE_RET_VAL(oStepDoc.LoadMemory(alg->GetUsageAsJSON()));
     373          67 :         jPipelineSteps.Add(oStepDoc.GetRoot());
     374             :     }
     375           7 :     oDoc.GetRoot().Add("pipeline_algorithms", jPipelineSteps);
     376             : 
     377          14 :     return oDoc.SaveAsString();
     378             : }
     379             : 
     380             : //! @endcond
     381             : 
     382             : #endif

Generated by: LCOV version 1.14