Coverage Report

Created: 2026-04-10 07:04

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