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