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_rename_layer.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  "rename-layer" step of "vector pipeline"
5
 * Author:   Even Rouault <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "gdalalg_vector_rename_layer.h"
14
15
//! @cond Doxygen_Suppress
16
17
#include <map>
18
19
#include "cpl_string.h"
20
21
#ifndef _
22
0
#define _(x) (x)
23
#endif
24
25
/************************************************************************/
26
/*   GDALVectorRenameLayerAlgorithm::GDALVectorRenameLayerAlgorithm()   */
27
/************************************************************************/
28
29
GDALVectorRenameLayerAlgorithm::GDALVectorRenameLayerAlgorithm(
30
    bool standaloneStep)
31
0
    : GDALVectorPipelineStepAlgorithm(
32
0
          NAME, DESCRIPTION, HELP_URL,
33
0
          ConstructorOptions()
34
0
              .SetStandaloneStep(standaloneStep)
35
0
              .SetAddInputLayerNameArgument(false)
36
0
              .SetOutputLayerNameAvailableInPipelineStep(true))
37
0
{
38
0
    AddLayerNameArg(&m_inputLayerName);
39
0
    if (!standaloneStep)
40
0
    {
41
0
        AddOutputLayerNameArg(/* hiddenForCLI = */ false,
42
0
                              /* shortNameOutputLayerAllowed = */ false);
43
0
    }
44
0
    AddArg("ascii", 0, _("Force names to ASCII character"), &m_ascii);
45
0
    AddArg("lower-case", 0,
46
0
           _("Force names to lower case (only on ASCII characters)"),
47
0
           &m_lowerCase);
48
0
    AddArg("filename-compatible", 0, _("Force names to be usable as filenames"),
49
0
           &m_filenameCompatible);
50
0
    AddArg("reserved-characters", 0, _("Reserved character(s) to be removed"),
51
0
           &m_reservedChars);
52
0
    AddArg("replacement-character", 0,
53
0
           _("Replacement character when ASCII conversion not possible"),
54
0
           &m_replacementChar)
55
0
        .SetMaxCharCount(1);
56
0
    AddArg("max-length", 0, _("Maximum length of layer names"), &m_maxLength)
57
0
        .SetMinValueIncluded(1);
58
59
0
    AddValidationAction(
60
0
        [this]()
61
0
        {
62
0
            if (!m_inputLayerName.empty() && m_outputLayerName.empty())
63
0
            {
64
0
                ReportError(CE_Failure, CPLE_AppDefined,
65
0
                            "Argument output-layer must be specified when "
66
0
                            "input-layer is specified");
67
0
                return false;
68
0
            }
69
70
0
            if (!m_inputDataset.empty() && m_inputDataset[0].GetDatasetRef())
71
0
            {
72
0
                auto poSrcDS = m_inputDataset[0].GetDatasetRef();
73
0
                if (!m_inputLayerName.empty() &&
74
0
                    poSrcDS->GetLayerByName(m_inputLayerName.c_str()) ==
75
0
                        nullptr)
76
0
                {
77
0
                    ReportError(CE_Failure, CPLE_AppDefined,
78
0
                                "Input layer '%s' does not exist",
79
0
                                m_inputLayerName.c_str());
80
0
                    return false;
81
0
                }
82
83
0
                if (!m_outputLayerName.empty() && m_inputLayerName.empty() &&
84
0
                    poSrcDS->GetLayerCount() >= 2)
85
0
                {
86
0
                    ReportError(CE_Failure, CPLE_AppDefined,
87
0
                                "Argument input-layer must be specified when "
88
0
                                "output-layer is specified and there is more "
89
0
                                "than one layer");
90
0
                    return false;
91
0
                }
92
0
            }
93
94
0
            return true;
95
0
        });
96
0
}
97
98
namespace
99
{
100
101
/************************************************************************/
102
/*                 GDALVectorRenameLayerAlgorithmLayer                  */
103
/************************************************************************/
104
105
class GDALVectorRenameLayerAlgorithmLayer final
106
    : public GDALVectorPipelineOutputLayer
107
{
108
  private:
109
    const OGRFeatureDefnRefCountedPtr m_poFeatureDefn;
110
111
    CPL_DISALLOW_COPY_ASSIGN(GDALVectorRenameLayerAlgorithmLayer)
112
113
    bool TranslateFeature(
114
        std::unique_ptr<OGRFeature> poSrcFeature,
115
        std::vector<std::unique_ptr<OGRFeature>> &apoOutFeatures) override
116
0
    {
117
0
        poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn.get());
118
0
        apoOutFeatures.push_back(std::move(poSrcFeature));
119
0
        return true;
120
0
    }
121
122
  public:
123
    explicit GDALVectorRenameLayerAlgorithmLayer(
124
        OGRLayer &oSrcLayer, const std::string &osOutputLayerName)
125
0
        : GDALVectorPipelineOutputLayer(oSrcLayer),
126
0
          m_poFeatureDefn(oSrcLayer.GetLayerDefn()->Clone())
127
0
    {
128
0
        m_poFeatureDefn->SetName(osOutputLayerName.c_str());
129
0
        const int nGeomFieldCount = m_poFeatureDefn->GetGeomFieldCount();
130
0
        const auto poSrcLayerDefn = oSrcLayer.GetLayerDefn();
131
0
        for (int i = 0; i < nGeomFieldCount; ++i)
132
0
        {
133
0
            m_poFeatureDefn->GetGeomFieldDefn(i)->SetSpatialRef(
134
0
                poSrcLayerDefn->GetGeomFieldDefn(i)->GetSpatialRef());
135
0
        }
136
0
        SetDescription(m_poFeatureDefn->GetName());
137
0
        SetMetadata(oSrcLayer.GetMetadata());
138
0
    }
139
140
    const OGRFeatureDefn *GetLayerDefn() const override
141
0
    {
142
0
        return m_poFeatureDefn.get();
143
0
    }
144
145
    GIntBig GetFeatureCount(int bForce) override
146
0
    {
147
0
        return m_srcLayer.GetFeatureCount(bForce);
148
0
    }
149
150
    OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
151
                      bool bForce) override
152
0
    {
153
0
        return m_srcLayer.GetExtent(iGeomField, psExtent, bForce);
154
0
    }
155
156
    OGRErr SetIgnoredFields(CSLConstList papszFields) override
157
0
    {
158
0
        return m_srcLayer.SetIgnoredFields(papszFields);
159
0
    }
160
161
    OGRErr SetAttributeFilter(const char *pszAttributeFilter) override
162
0
    {
163
0
        OGRLayer::SetAttributeFilter(pszAttributeFilter);
164
0
        return m_srcLayer.SetAttributeFilter(pszAttributeFilter);
165
0
    }
166
167
    OGRGeometry *GetSpatialFilter() override
168
0
    {
169
0
        return m_srcLayer.GetSpatialFilter();
170
0
    }
171
172
    OGRErr ISetSpatialFilter(int iGeomField, const OGRGeometry *poGeom) override
173
0
    {
174
0
        return m_srcLayer.SetSpatialFilter(iGeomField, poGeom);
175
0
    }
176
177
    OGRFeature *GetFeature(GIntBig nFID) override
178
0
    {
179
0
        auto poSrcFeature =
180
0
            std::unique_ptr<OGRFeature>(m_srcLayer.GetFeature(nFID));
181
0
        if (!poSrcFeature)
182
0
            return nullptr;
183
0
        poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn.get());
184
0
        return poSrcFeature.release();
185
0
    }
186
187
    int TestCapability(const char *pszCap) const override
188
0
    {
189
0
        return m_srcLayer.TestCapability(pszCap);
190
0
    }
191
};
192
193
/************************************************************************/
194
/*                GDALVectorRenameLayerAlgorithmDataset                 */
195
/************************************************************************/
196
197
class GDALVectorRenameLayerAlgorithmDataset final
198
    : public GDALVectorPipelineOutputDataset
199
{
200
  public:
201
    GDALVectorRenameLayerAlgorithmDataset(
202
        GDALDataset &oSrcDS, const std::vector<std::string> &aosNewLayerNames)
203
0
        : GDALVectorPipelineOutputDataset(oSrcDS)
204
0
    {
205
0
        const int nLayerCount = oSrcDS.GetLayerCount();
206
0
        CPLAssert(aosNewLayerNames.size() == static_cast<size_t>(nLayerCount));
207
0
        for (int i = 0; i < nLayerCount; ++i)
208
0
        {
209
0
            m_mapOldLayerNameToNew[oSrcDS.GetLayer(i)->GetName()] =
210
0
                aosNewLayerNames[i];
211
0
        }
212
0
    }
213
214
    const GDALRelationship *
215
    GetRelationship(const std::string &name) const override;
216
217
  private:
218
    std::map<std::string, std::string> m_mapOldLayerNameToNew{};
219
    mutable std::map<std::string, std::unique_ptr<GDALRelationship>>
220
        m_relationships{};
221
};
222
223
/************************************************************************/
224
/*                          GetRelationship()                           */
225
/************************************************************************/
226
227
const GDALRelationship *GDALVectorRenameLayerAlgorithmDataset::GetRelationship(
228
    const std::string &name) const
229
0
{
230
0
    const auto oIterRelationships = m_relationships.find(name);
231
0
    if (oIterRelationships != m_relationships.end())
232
0
        return oIterRelationships->second.get();
233
234
0
    const GDALRelationship *poSrcRelationShip = m_srcDS.GetRelationship(name);
235
0
    if (!poSrcRelationShip)
236
0
        return nullptr;
237
0
    const auto oIterLeftTableName =
238
0
        m_mapOldLayerNameToNew.find(poSrcRelationShip->GetLeftTableName());
239
0
    const auto oIterRightTableName =
240
0
        m_mapOldLayerNameToNew.find(poSrcRelationShip->GetRightTableName());
241
0
    const auto oIterMappingTableName =
242
0
        m_mapOldLayerNameToNew.find(poSrcRelationShip->GetMappingTableName());
243
0
    if (oIterLeftTableName == m_mapOldLayerNameToNew.end() &&
244
0
        oIterRightTableName == m_mapOldLayerNameToNew.end() &&
245
0
        oIterMappingTableName == m_mapOldLayerNameToNew.end())
246
0
    {
247
0
        return poSrcRelationShip;
248
0
    }
249
250
0
    auto poNewRelationship =
251
0
        std::make_unique<GDALRelationship>(*poSrcRelationShip);
252
0
    if (oIterLeftTableName != m_mapOldLayerNameToNew.end())
253
0
        poNewRelationship->SetLeftTableName(oIterLeftTableName->second);
254
0
    if (oIterRightTableName != m_mapOldLayerNameToNew.end())
255
0
        poNewRelationship->SetRightTableName(oIterRightTableName->second);
256
0
    if (oIterMappingTableName != m_mapOldLayerNameToNew.end())
257
0
        poNewRelationship->SetMappingTableName(oIterMappingTableName->second);
258
259
0
    return m_relationships.insert({name, std::move(poNewRelationship)})
260
0
        .first->second.get();
261
0
}
262
263
}  // namespace
264
265
/************************************************************************/
266
/*                       TruncateUTF8ToMaxChar()                        */
267
/************************************************************************/
268
269
static void TruncateUTF8ToMaxChar(std::string &osStr, size_t maxCharCount)
270
0
{
271
0
    size_t nCharacterCount = 0;
272
0
    for (size_t i = 0; i < osStr.size(); ++i)
273
0
    {
274
        // Is it first byte of a UTF-8 character?
275
0
        if ((osStr[i] & 0xc0) != 0x80)
276
0
        {
277
0
            ++nCharacterCount;
278
0
            if (nCharacterCount == maxCharCount)
279
0
            {
280
0
                osStr.resize(i + 1);
281
0
                break;
282
0
            }
283
0
        }
284
0
    }
285
0
}
286
287
/************************************************************************/
288
/*              GDALVectorRenameLayerAlgorithm::RunStep()               */
289
/************************************************************************/
290
291
bool GDALVectorRenameLayerAlgorithm::RunStep(GDALPipelineStepRunContext &)
292
0
{
293
0
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
294
0
    CPLAssert(poSrcDS);
295
296
0
    CPLAssert(m_outputDataset.GetName().empty());
297
0
    CPLAssert(!m_outputDataset.GetDatasetRef());
298
299
    // First pass over layer names to create new layer names matching specified
300
    // constraints
301
0
    std::vector<std::string> aosNames;
302
0
    std::map<std::string, int> oMapCountNames;
303
0
    bool bNonUniqueNames = false;
304
0
    const int nLayerCount = poSrcDS->GetLayerCount();
305
0
    for (const OGRLayer *poSrcLayer : poSrcDS->GetLayers())
306
0
    {
307
0
        if ((m_inputLayerName == poSrcLayer->GetDescription() ||
308
0
             nLayerCount == 1) &&
309
0
            !m_outputLayerName.empty())
310
0
        {
311
0
            aosNames.push_back(m_outputLayerName);
312
0
        }
313
0
        else
314
0
        {
315
0
            std::string osName(poSrcLayer->GetDescription());
316
0
            if (!m_reservedChars.empty())
317
0
            {
318
0
                std::string osNewName;
319
0
                for (char c : osName)
320
0
                {
321
0
                    if (m_reservedChars.find(c) != std::string::npos)
322
0
                    {
323
0
                        if (!m_replacementChar.empty())
324
0
                            osNewName += m_replacementChar;
325
0
                    }
326
0
                    else
327
0
                    {
328
0
                        osNewName += c;
329
0
                    }
330
0
                }
331
0
                osName = std::move(osNewName);
332
0
            }
333
0
            if (m_filenameCompatible)
334
0
            {
335
0
                osName = CPLLaunderForFilenameSafe(
336
0
                    osName, m_replacementChar.c_str()[0]);
337
0
            }
338
0
            if (m_ascii)
339
0
            {
340
0
                char *pszStr = CPLUTF8ForceToASCII(
341
0
                    osName.c_str(), m_replacementChar.c_str()[0]);
342
0
                osName = pszStr;
343
0
                CPLFree(pszStr);
344
0
            }
345
0
            if (m_lowerCase)
346
0
            {
347
0
                for (char &c : osName)
348
0
                {
349
0
                    if (c >= 'A' && c <= 'Z')
350
0
                        c = c - 'A' + 'a';
351
0
                }
352
0
            }
353
0
            if (m_maxLength > 0)
354
0
            {
355
0
                TruncateUTF8ToMaxChar(osName, m_maxLength);
356
0
            }
357
0
            if (++oMapCountNames[osName] > 1)
358
0
                bNonUniqueNames = true;
359
0
            aosNames.push_back(std::move(osName));
360
0
        }
361
0
    }
362
363
    // Extra optional pass if some names are not unique
364
0
    if (bNonUniqueNames)
365
0
    {
366
0
        std::map<std::string, int> oMapCurCounter;
367
0
        bool bUniquenessPossible = true;
368
0
        for (auto &osName : aosNames)
369
0
        {
370
0
            const int nCountForName = oMapCountNames[osName];
371
0
            if (nCountForName > 1)
372
0
            {
373
0
                const int nCounter = ++oMapCurCounter[osName];
374
0
                std::string osSuffix("_");
375
0
                if (nCountForName <= 9)
376
0
                    osSuffix += CPLSPrintf("%d", nCounter);
377
0
                else if (nCountForName <= 99)
378
0
                    osSuffix += CPLSPrintf("%02d", nCounter);
379
0
                else
380
0
                    osSuffix += CPLSPrintf("%03d", nCounter);
381
0
                const size_t nNameLen = CPLStrlenUTF8Ex(osName.c_str());
382
0
                if (m_maxLength > 0 && nNameLen + osSuffix.size() >
383
0
                                           static_cast<size_t>(m_maxLength))
384
0
                {
385
0
                    if (nNameLen > osSuffix.size())
386
0
                    {
387
0
                        TruncateUTF8ToMaxChar(osName,
388
0
                                              nNameLen - osSuffix.size());
389
0
                        osName += osSuffix;
390
0
                    }
391
0
                    else if (bUniquenessPossible)
392
0
                    {
393
0
                        ReportError(CE_Warning, CPLE_AppDefined,
394
0
                                    "Cannot create unique name for '%s' while "
395
0
                                    "respecting %d maximum length",
396
0
                                    osName.c_str(), m_maxLength);
397
0
                        bUniquenessPossible = false;
398
0
                    }
399
0
                }
400
0
                else
401
0
                {
402
0
                    osName += osSuffix;
403
0
                }
404
0
            }
405
0
        }
406
0
    }
407
408
0
    auto outDS = std::make_unique<GDALVectorRenameLayerAlgorithmDataset>(
409
0
        *poSrcDS, aosNames);
410
411
    // Final pass to create output layers
412
0
    size_t i = 0;
413
0
    for (OGRLayer *poSrcLayer : poSrcDS->GetLayers())
414
0
    {
415
0
        if (poSrcLayer->GetDescription() != aosNames[i])
416
0
        {
417
0
            auto poLayer =
418
0
                std::make_unique<GDALVectorRenameLayerAlgorithmLayer>(
419
0
                    *poSrcLayer, aosNames[i]);
420
0
            outDS->AddLayer(*poSrcLayer, std::move(poLayer));
421
0
        }
422
0
        else
423
0
        {
424
0
            outDS->AddLayer(
425
0
                *poSrcLayer,
426
0
                std::make_unique<GDALVectorPipelinePassthroughLayer>(
427
0
                    *poSrcLayer));
428
0
        }
429
0
        ++i;
430
0
    }
431
432
0
    m_outputDataset.Set(std::move(outDS));
433
434
0
    return true;
435
0
}
436
437
GDALVectorRenameLayerAlgorithmStandalone::
438
0
    ~GDALVectorRenameLayerAlgorithmStandalone() = default;
439
440
//! @endcond