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