Coverage Report

Created: 2026-07-25 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/apps/gdalalg_vector_create.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  gdal "vector create" subcommand
5
 * Author:   Alessandro Pasotti <elpaso at itopen dot it>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2026, Alessandro Pasotti <elpaso at itopen dot it>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include <regex>
14
#include "gdalalg_vector_create.h"
15
#include "gdal_utils.h"
16
#include "ogr_schema_override.h"
17
18
//! @cond Doxygen_Suppress
19
20
#ifndef _
21
0
#define _(x) (x)
22
#endif
23
24
/************************************************************************/
25
/*        GDALVectorCreateAlgorithm::GDALVectorCreateAlgorithm()        */
26
/************************************************************************/
27
28
GDALVectorCreateAlgorithm::GDALVectorCreateAlgorithm(bool standaloneStep)
29
0
    : GDALVectorPipelineStepAlgorithm(
30
0
          NAME, DESCRIPTION, HELP_URL,
31
0
          ConstructorOptions()
32
0
              .SetStandaloneStep(standaloneStep)
33
0
              .SetOutputFormatCreateCapability(GDAL_DCAP_CREATE)
34
35
              // Remove defaults because input is the optional template
36
0
              .SetAddDefaultArguments(false)
37
38
              // For --like input template
39
0
              .SetAutoOpenInputDatasets(true)
40
0
              .SetInputDatasetHelpMsg(_("Template vector dataset"))
41
0
              .SetInputDatasetAlias("like")
42
0
              .SetInputDatasetRequired(false)
43
0
              .SetInputDatasetPositional(false)
44
0
              .SetInputDatasetMaxCount(1)
45
0
              .SetInputDatasetMetaVar("TEMPLATE-DATASET")
46
47
              // Remove arguments that don't make sense in a create context
48
              // Note: this is required despite SetAddDefaultArguments(false)
49
0
              .SetAddUpsertArgument(false)
50
0
              .SetAddSkipErrorsArgument(false)
51
0
              .SetAddAppendLayerArgument(false))
52
0
{
53
0
    if (standaloneStep)
54
0
        AddProgressArg(/* hidden = */ true);
55
56
0
    AddVectorInputArgs(false);
57
0
    AddVectorOutputArgs(/* hiddenForCLI = */ false,
58
0
                        /* shortNameOutputLayerAllowed=*/false);
59
0
    AddGeometryTypeArg(&m_geometryType, _("Layer geometry type"));
60
61
    // Add optional geometry field name argument, not all drivers support it, and if not specified, the default "geom" name will be used.
62
0
    auto &geomFieldNameArg =
63
0
        AddArg("geometry-field", 0,
64
0
               _("Name of the geometry field to create (if supported by the "
65
0
                 "output format)"),
66
0
               &m_geometryFieldName)
67
0
            .SetMetaVar("GEOMETRY-FIELD")
68
0
            .SetDefault(m_geometryFieldName);
69
70
0
    AddArg("crs", 0, _("Set CRS"), &m_crs)
71
0
        .AddHiddenAlias("srs")
72
0
        .SetIsCRSArg(/*noneAllowed=*/false);
73
74
0
    AddArg("fid", 0, _("FID column name"), &m_fidColumnName);
75
76
0
    constexpr auto inputMutexGroup = "like-schema-field";
77
78
    // Apply mutex to GDAL_ARG_NAME_INPUT
79
    // This is hackish and I really don't like const_cast but I couldn't find another way.
80
0
    const_cast<GDALAlgorithmArgDecl &>(
81
0
        GetArg(GDAL_ARG_NAME_INPUT)->GetDeclaration())
82
0
        .SetMutualExclusionGroup(inputMutexGroup);
83
84
    // Add --schema argument to read OGR_SCHEMA and populate field definitions from it. It is mutually exclusive with --like and --field arguments.
85
0
    AddArg("schema", 0,
86
0
           _("Read OGR_SCHEMA and populate field definitions from it"),
87
0
           &m_schemaJsonOrPath)
88
0
        .SetMetaVar("SCHEMA_JSON")
89
0
        .SetRepeatedArgAllowed(false)
90
0
        .SetMutualExclusionGroup(inputMutexGroup);
91
92
    // Add field definition argument
93
0
    AddFieldDefinitionArg(&m_fieldStrDefinitions, &m_fieldDefinitions,
94
0
                          _("Add a field definition to the output layer"))
95
0
        .SetMetaVar("<NAME>:<TYPE>[(,<WIDTH>[,<PRECISION>])]")
96
0
        .SetPackedValuesAllowed(false)
97
0
        .SetRepeatedArgAllowed(true)
98
0
        .SetMutualExclusionGroup(inputMutexGroup);
99
100
0
    AddValidationAction(
101
0
        [this, &geomFieldNameArg]()
102
0
        {
103
0
            if ((!m_schemaJsonOrPath.empty() || !m_inputDataset.empty()) &&
104
0
                ((!m_geometryFieldName.empty() &&
105
0
                  geomFieldNameArg.IsExplicitlySet()) ||
106
0
                 !m_geometryType.empty() || !m_fieldDefinitions.empty() ||
107
0
                 !m_crs.empty() || !m_fidColumnName.empty()))
108
0
            {
109
0
                ReportError(CE_Failure, CPLE_AppDefined,
110
0
                            "When --schema or --like is specified, "
111
0
                            "--geometry-field, --geometry-type, --field, "
112
0
                            "--crs and --fid options must not be specified.");
113
0
                return false;
114
0
            }
115
0
            return true;
116
0
        });
117
0
}
118
119
/************************************************************************/
120
/*                 GDALVectorCreateAlgorithm::RunStep()                 */
121
/************************************************************************/
122
123
bool GDALVectorCreateAlgorithm::RunStep(GDALPipelineStepRunContext &)
124
0
{
125
126
0
    const std::string &datasetName = m_outputDataset.GetName();
127
0
    const std::string outputLayerName =
128
0
        m_outputLayerName.empty() ? CPLGetBasenameSafe(datasetName.c_str())
129
0
                                  : m_outputLayerName;
130
131
0
    std::unique_ptr<GDALDataset> poDstDS;
132
0
    poDstDS.reset(GDALDataset::Open(datasetName.c_str(),
133
0
                                    GDAL_OF_VECTOR | GDAL_OF_UPDATE, nullptr,
134
0
                                    nullptr, nullptr));
135
136
0
    if (poDstDS && !m_update)
137
0
    {
138
0
        ReportError(CE_Failure, CPLE_AppDefined,
139
0
                    "Dataset %s already exists. Specify the "
140
0
                    "--%s option to open it in update mode.",
141
0
                    datasetName.c_str(), GDAL_ARG_NAME_UPDATE);
142
0
        return false;
143
0
    }
144
145
0
    GDALDataset *poSrcDS = m_inputDataset.empty()
146
0
                               ? nullptr
147
0
                               : m_inputDataset.front().GetDatasetRef();
148
149
0
    OGRSchemaOverride oSchemaOverride;
150
151
0
    const auto loadJSON = [this,
152
0
                           &oSchemaOverride](const std::string &source) -> bool
153
0
    {
154
        // This error count is necessary because LoadFromJSON tries to load
155
        // the content as a file first (and set an error it if fails) then tries
156
        // to load as a JSON string but even if it succeeds an error is still
157
        // set and not cleared.
158
0
        const auto nErrorCount = CPLGetErrorCounter();
159
0
        if (!oSchemaOverride.LoadFromJSON(source,
160
0
                                          /* allowGeometryFields */ true))
161
0
        {
162
            // Get the last error message and report it, since LoadFromJSON doesn't do it itself.
163
0
            if (nErrorCount != CPLGetErrorCounter())
164
0
            {
165
0
                const std::string lastErrorMsg = CPLGetLastErrorMsg();
166
0
                CPLErrorReset();
167
0
                ReportError(CE_Failure, CPLE_AppDefined,
168
0
                            "Cannot parse OGR_SCHEMA: %s.",
169
0
                            lastErrorMsg.c_str());
170
0
            }
171
0
            else
172
0
            {
173
0
                ReportError(CE_Failure, CPLE_AppDefined,
174
0
                            "Cannot parse OGR_SCHEMA (unknown error).");
175
0
            }
176
0
            return false;
177
0
        }
178
0
        else if (nErrorCount != CPLGetErrorCounter())
179
0
        {
180
0
            CPLErrorReset();
181
0
        }
182
0
        return true;
183
0
    };
184
185
    // Use the input dataset as to create an OGR_SCHEMA
186
0
    if (poSrcDS)
187
0
    {
188
        // Export the schema using GDALVectorInfo
189
0
        CPLStringList aosOptions;
190
191
0
        aosOptions.AddString("-schema");
192
193
        // Must be last, as positional
194
0
        aosOptions.AddString("dummy");
195
0
        aosOptions.AddString("-al");
196
197
0
        GDALVectorInfoOptions *psInfo =
198
0
            GDALVectorInfoOptionsNew(aosOptions.List(), nullptr);
199
200
0
        char *ret = GDALVectorInfo(GDALDataset::ToHandle(poSrcDS), psInfo);
201
0
        GDALVectorInfoOptionsFree(psInfo);
202
0
        if (!ret)
203
0
            return false;
204
205
0
        if (!loadJSON(ret))
206
0
        {
207
0
            CPLFree(ret);
208
0
            return false;
209
0
        }
210
0
        CPLFree(ret);
211
0
    }
212
0
    else if (!m_schemaJsonOrPath.empty() && !loadJSON(m_schemaJsonOrPath))
213
0
    {
214
0
        return false;
215
0
    }
216
217
0
    if (m_standaloneStep)
218
0
    {
219
0
        if (m_format.empty())
220
0
        {
221
0
            const auto aosFormats =
222
0
                CPLStringList(GDALGetOutputDriversForDatasetName(
223
0
                    m_outputDataset.GetName().c_str(), GDAL_OF_VECTOR,
224
0
                    /* bSingleMatch = */ true,
225
0
                    /* bWarn = */ true));
226
0
            if (aosFormats.size() != 1)
227
0
            {
228
0
                ReportError(CE_Failure, CPLE_AppDefined,
229
0
                            "Cannot guess driver for %s",
230
0
                            m_outputDataset.GetName().c_str());
231
0
                return false;
232
0
            }
233
0
            m_format = aosFormats[0];
234
0
        }
235
0
    }
236
0
    else
237
0
    {
238
0
        m_format = "MEM";
239
0
    }
240
241
0
    auto poDstDriver =
242
0
        GetGDALDriverManager()->GetDriverByName(m_format.c_str());
243
0
    if (!poDstDriver)
244
0
    {
245
0
        ReportError(CE_Failure, CPLE_AppDefined, "Cannot find driver %s.",
246
0
                    m_format.c_str());
247
0
        return false;
248
0
    }
249
250
0
    if (!poDstDS)
251
0
        poDstDS.reset(poDstDriver->Create(datasetName.c_str(), 0, 0, 0,
252
0
                                          GDT_Unknown,
253
0
                                          CPLStringList(m_creationOptions)));
254
255
0
    if (!poDstDS)
256
0
    {
257
0
        ReportError(CE_Failure, CPLE_AppDefined, "Cannot create dataset %s.",
258
0
                    datasetName.c_str());
259
0
        return false;
260
0
    }
261
262
    // An OGR_SCHEMA has been provided
263
0
    if (!oSchemaOverride.GetLayerOverrides().empty())
264
0
    {
265
        // Checks if input layer names were specified and the layers exists in the schema
266
0
        if (!m_inputLayerNames.empty())
267
0
        {
268
0
            for (const auto &inputLayerName : m_inputLayerNames)
269
0
            {
270
0
                if (!oSchemaOverride.GetLayerOverride(inputLayerName).IsValid())
271
0
                {
272
0
                    ReportError(CE_Failure, CPLE_AppDefined,
273
0
                                "The specified input layer name '%s' doesn't "
274
0
                                "exist in the provided template or schema.",
275
0
                                inputLayerName.c_str());
276
0
                    return false;
277
0
                }
278
0
            }
279
0
        }
280
281
        // If there are multiple layers check if the destination format supports
282
        // multiple layers, and if not, error out.
283
0
        if (oSchemaOverride.GetLayerOverrides().size() > 1 &&
284
0
            !GDALGetMetadataItem(poDstDriver, GDAL_DCAP_MULTIPLE_VECTOR_LAYERS,
285
0
                                 nullptr) &&
286
0
            m_inputLayerNames.size() != 1)
287
0
        {
288
0
            ReportError(CE_Failure, CPLE_AppDefined,
289
0
                        "The output format %s doesn't support multiple layers.",
290
0
                        poDstDriver->GetDescription());
291
0
            return false;
292
0
        }
293
294
        // If output layer name was specified and there is more than one layer in the schema,
295
        // error out since we won't know which layer to apply it to
296
0
        if (!m_outputLayerName.empty() &&
297
0
            oSchemaOverride.GetLayerOverrides().size() > 1 &&
298
0
            m_inputLayerNames.size() != 1)
299
0
        {
300
0
            ReportError(CE_Failure, CPLE_AppDefined,
301
0
                        "Output layer name should not be specified when there "
302
0
                        "are multiple layers in the schema.");
303
0
            return false;
304
0
        }
305
306
0
        std::vector<std::string> layersToBeCreated;
307
0
        for (const auto &oLayerOverride : oSchemaOverride.GetLayerOverrides())
308
0
        {
309
310
0
            if (!m_inputLayerNames.empty() &&
311
0
                std::find(m_inputLayerNames.begin(), m_inputLayerNames.end(),
312
0
                          oLayerOverride.GetLayerName()) ==
313
0
                    m_inputLayerNames.end())
314
0
            {
315
                // This layer is not in the list of input layers to consider, so skip it
316
0
                continue;
317
0
            }
318
0
            layersToBeCreated.push_back(oLayerOverride.GetLayerName());
319
0
        }
320
321
        // Loop over layers in the OGR_SCHEMA and create them
322
0
        for (const auto &layerToCreate : layersToBeCreated)
323
0
        {
324
0
            const auto &oLayerOverride =
325
0
                oSchemaOverride.GetLayerOverride(layerToCreate);
326
0
            if (!oLayerOverride.IsValid())
327
0
            {
328
0
                ReportError(CE_Failure, CPLE_AppDefined,
329
0
                            "Invalid layer override for layer '%s'.",
330
0
                            layerToCreate.c_str());
331
0
                return false;
332
0
            }
333
334
            // We can use the defined layer name only if there is a single layer to be created
335
0
            const std::string userSpecifiedNewName =
336
0
                !m_outputLayerName.empty() ? m_outputLayerName
337
0
                                           : oLayerOverride.GetLayerName();
338
0
            const std::string outputLayerNewName =
339
0
                layersToBeCreated.size() > 1 ? oLayerOverride.GetLayerName()
340
0
                                             : userSpecifiedNewName;
341
342
0
            if (!CreateLayer(poDstDS.get(), outputLayerNewName,
343
0
                             oLayerOverride.GetFIDColumnName(),
344
0
                             oLayerOverride.GetFieldDefinitions(),
345
0
                             oLayerOverride.GetGeomFieldDefinitions()))
346
0
            {
347
0
                ReportError(CE_Failure, CPLE_AppDefined,
348
0
                            "Cannot create layer '%s'",
349
0
                            oLayerOverride.GetLayerName().c_str());
350
0
                return false;
351
0
            }
352
0
        }
353
0
    }
354
0
    else
355
0
    {
356
0
        std::vector<OGRGeomFieldDefn> geometryFieldDefinitions;
357
0
        if (!m_geometryType.empty())
358
0
        {
359
0
            const OGRwkbGeometryType eDstType =
360
0
                OGRFromOGCGeomType(m_geometryType.c_str());
361
0
            if (eDstType == wkbUnknown &&
362
0
                !STARTS_WITH_CI(m_geometryType.c_str(), "GEOMETRY"))
363
0
            {
364
0
                ReportError(CE_Failure, CPLE_AppDefined,
365
0
                            "Unsupported geometry type: '%s'.",
366
0
                            m_geometryType.c_str());
367
0
                return false;
368
0
            }
369
0
            else
370
0
            {
371
0
                OGRGeomFieldDefn oGeomFieldDefn(m_geometryFieldName.c_str(),
372
0
                                                eDstType);
373
0
                if (!m_crs.empty())
374
0
                {
375
0
                    auto poSRS =
376
0
                        OGRSpatialReferenceRefCountedPtr::makeInstance();
377
0
                    if (poSRS->SetFromUserInput(m_crs.c_str()) != OGRERR_NONE)
378
0
                    {
379
0
                        ReportError(CE_Failure, CPLE_AppDefined,
380
0
                                    "Cannot parse CRS definition: '%s'.",
381
0
                                    m_crs.c_str());
382
0
                        return false;
383
0
                    }
384
0
                    else
385
0
                    {
386
0
                        oGeomFieldDefn.SetSpatialRef(poSRS.get());
387
0
                    }
388
0
                }
389
0
                geometryFieldDefinitions.push_back(std::move(oGeomFieldDefn));
390
0
            }
391
0
        }
392
393
0
        if (!CreateLayer(poDstDS.get(), outputLayerName, m_fidColumnName,
394
0
                         GetOutputFields(), geometryFieldDefinitions))
395
0
        {
396
0
            ReportError(CE_Failure, CPLE_AppDefined,
397
0
                        "Cannot create layer '%s'.", outputLayerName.c_str());
398
0
            return false;
399
0
        }
400
0
    }
401
402
0
    m_outputDataset.Set(std::move(poDstDS));
403
0
    return true;
404
0
}
405
406
/************************************************************************/
407
/*                 GDALVectorCreateAlgorithm::RunImpl()                 */
408
/************************************************************************/
409
bool GDALVectorCreateAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
410
                                        void *pProgressData)
411
0
{
412
0
    GDALPipelineStepRunContext stepCtxt;
413
0
    stepCtxt.m_pfnProgress = pfnProgress;
414
0
    stepCtxt.m_pProgressData = pProgressData;
415
0
    return RunPreStepPipelineValidations() && RunStep(stepCtxt);
416
0
}
417
418
/************************************************************************/
419
/*             GDALVectorCreateAlgorithm::GetOutputFields()             */
420
/************************************************************************/
421
std::vector<OGRFieldDefn> GDALVectorCreateAlgorithm::GetOutputFields() const
422
0
{
423
    // This is where we will eventually implement override logic to modify field
424
    // definitions based on input dataset and/or OGR_SCHEMA, but for now we just
425
    // return the field definitions as specified by the user through --field arguments.
426
0
    return m_fieldDefinitions;
427
0
}
428
429
/************************************************************************/
430
/*               GDALVectorCreateAlgorithm::CreateLayer()               */
431
/************************************************************************/
432
bool GDALVectorCreateAlgorithm::CreateLayer(
433
    GDALDataset *poDstDS, const std::string &layerName,
434
    const std::string &fidColumnName,
435
    const std::vector<OGRFieldDefn> &fieldDefinitions,
436
    const std::vector<OGRGeomFieldDefn> &geometryFieldDefinitions) const
437
0
{
438
0
    if (auto poExistingDstLayer = poDstDS->GetLayerByName(layerName.c_str()))
439
0
    {
440
0
        if (GetOverwriteLayer())
441
0
        {
442
0
            int iLayer = -1;
443
0
            const int nLayerCount = poDstDS->GetLayerCount();
444
0
            for (iLayer = 0; iLayer < nLayerCount; iLayer++)
445
0
            {
446
0
                if (poDstDS->GetLayer(iLayer) == poExistingDstLayer)
447
0
                    break;
448
0
            }
449
450
0
            if (iLayer < nLayerCount)
451
0
            {
452
0
                if (poDstDS->DeleteLayer(iLayer) != OGRERR_NONE)
453
0
                {
454
0
                    ReportError(CE_Failure, CPLE_AppDefined,
455
0
                                "Cannot delete layer '%s'.", layerName.c_str());
456
0
                    return false;
457
0
                }
458
0
            }
459
0
        }
460
0
        else
461
0
        {
462
0
            ReportError(CE_Failure, CPLE_AppDefined,
463
0
                        "Layer '%s' already exists. Specify the "
464
0
                        "--%s option to overwrite it.",
465
0
                        layerName.c_str(), GDAL_ARG_NAME_OVERWRITE_LAYER);
466
0
            return false;
467
0
        }
468
0
    }
469
0
    else if (GetOverwriteLayer())
470
0
    {
471
0
        ReportError(CE_Failure, CPLE_AppDefined, "Cannot find layer '%s'.",
472
0
                    layerName.c_str());
473
0
        return false;
474
0
    }
475
476
    // Get the geometry field definition, if any
477
0
    std::unique_ptr<OGRGeomFieldDefn> poGeomFieldDefn;
478
0
    if (!geometryFieldDefinitions.empty())
479
0
    {
480
0
        if (geometryFieldDefinitions.size() > 1)
481
0
        {
482
            // NOTE: this limitation may eventually be removed,
483
            // but for now we don't want to deal with the complexity
484
            // of creating multiple geometry fields with various drivers that
485
            // may or may not support it
486
0
            ReportError(CE_Failure, CPLE_AppDefined,
487
0
                        "Multiple geometry fields are not supported.");
488
0
            return false;
489
0
        }
490
0
        poGeomFieldDefn =
491
0
            std::make_unique<OGRGeomFieldDefn>(geometryFieldDefinitions[0]);
492
0
    }
493
494
0
    CPLStringList aosCreationOptions(GetLayerCreationOptions());
495
0
    if (aosCreationOptions.FetchNameValue("FID") == nullptr &&
496
0
        !fidColumnName.empty())
497
0
    {
498
0
        auto poDstDriver = poDstDS->GetDriver();
499
0
        if (poDstDriver && poDstDriver->HasLayerCreationOption("FID"))
500
0
        {
501
0
            aosCreationOptions.SetNameValue("FID", fidColumnName.c_str());
502
0
        }
503
0
    }
504
0
    auto poDstLayer = poDstDS->CreateLayer(
505
0
        layerName.c_str(), poGeomFieldDefn.get(), aosCreationOptions.List());
506
0
    if (!poDstLayer)
507
0
    {
508
0
        ReportError(CE_Failure, CPLE_AppDefined, "Cannot create layer '%s'.",
509
0
                    layerName.c_str());
510
0
        return false;
511
0
    }
512
513
0
    for (const auto &oFieldDefn : fieldDefinitions)
514
0
    {
515
0
        if (poDstLayer->CreateField(&oFieldDefn) != OGRERR_NONE)
516
0
        {
517
0
            ReportError(CE_Failure, CPLE_AppDefined,
518
0
                        "Cannot create field '%s' in layer '%s'.",
519
0
                        oFieldDefn.GetNameRef(), layerName.c_str());
520
0
            return false;
521
0
        }
522
0
    }
523
524
0
    return true;
525
0
}
526
527
/************************************************************************/
528
/*                ~GDALVectorCreateAlgorithmStandalone()                */
529
/************************************************************************/
530
0
GDALVectorCreateAlgorithmStandalone::~GDALVectorCreateAlgorithmStandalone() =
531
    default;
532
533
//! @endcond