Coverage Report

Created: 2026-04-01 06:20

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