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_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
    OGRFeatureDefn *const m_poFeatureDefn = nullptr;
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);
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
        m_poFeatureDefn->Reference();
136
0
    }
137
138
    ~GDALVectorRenameLayerAlgorithmLayer() override
139
0
    {
140
0
        m_poFeatureDefn->Release();
141
0
    }
142
143
    const OGRFeatureDefn *GetLayerDefn() const override
144
0
    {
145
0
        return m_poFeatureDefn;
146
0
    }
147
148
    GIntBig GetFeatureCount(int bForce) override
149
0
    {
150
0
        return m_srcLayer.GetFeatureCount(bForce);
151
0
    }
152
153
    OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
154
                      bool bForce) override
155
0
    {
156
0
        return m_srcLayer.GetExtent(iGeomField, psExtent, bForce);
157
0
    }
158
159
    OGRErr SetIgnoredFields(CSLConstList papszFields) override
160
0
    {
161
0
        return m_srcLayer.SetIgnoredFields(papszFields);
162
0
    }
163
164
    OGRErr SetAttributeFilter(const char *pszAttributeFilter) override
165
0
    {
166
0
        OGRLayer::SetAttributeFilter(pszAttributeFilter);
167
0
        return m_srcLayer.SetAttributeFilter(pszAttributeFilter);
168
0
    }
169
170
    OGRGeometry *GetSpatialFilter() override
171
0
    {
172
0
        return m_srcLayer.GetSpatialFilter();
173
0
    }
174
175
    OGRErr ISetSpatialFilter(int iGeomField, const OGRGeometry *poGeom) override
176
0
    {
177
0
        return m_srcLayer.SetSpatialFilter(iGeomField, poGeom);
178
0
    }
179
180
    OGRFeature *GetFeature(GIntBig nFID) override
181
0
    {
182
0
        auto poSrcFeature =
183
0
            std::unique_ptr<OGRFeature>(m_srcLayer.GetFeature(nFID));
184
0
        if (!poSrcFeature)
185
0
            return nullptr;
186
0
        poSrcFeature->SetFDefnUnsafe(m_poFeatureDefn);
187
0
        return poSrcFeature.release();
188
0
    }
189
190
    int TestCapability(const char *pszCap) const override
191
0
    {
192
0
        return m_srcLayer.TestCapability(pszCap);
193
0
    }
194
};
195
196
/************************************************************************/
197
/*                GDALVectorRenameLayerAlgorithmDataset                 */
198
/************************************************************************/
199
200
class GDALVectorRenameLayerAlgorithmDataset final
201
    : public GDALVectorPipelineOutputDataset
202
{
203
  public:
204
    GDALVectorRenameLayerAlgorithmDataset(
205
        GDALDataset &oSrcDS, const std::vector<std::string> &aosNewLayerNames)
206
0
        : GDALVectorPipelineOutputDataset(oSrcDS)
207
0
    {
208
0
        const int nLayerCount = oSrcDS.GetLayerCount();
209
0
        CPLAssert(aosNewLayerNames.size() == static_cast<size_t>(nLayerCount));
210
0
        for (int i = 0; i < nLayerCount; ++i)
211
0
        {
212
0
            m_mapOldLayerNameToNew[oSrcDS.GetLayer(i)->GetName()] =
213
0
                aosNewLayerNames[i];
214
0
        }
215
0
    }
216
217
    const GDALRelationship *
218
    GetRelationship(const std::string &name) const override;
219
220
  private:
221
    std::map<std::string, std::string> m_mapOldLayerNameToNew{};
222
    mutable std::map<std::string, std::unique_ptr<GDALRelationship>>
223
        m_relationships{};
224
};
225
226
/************************************************************************/
227
/*                          GetRelationship()                           */
228
/************************************************************************/
229
230
const GDALRelationship *GDALVectorRenameLayerAlgorithmDataset::GetRelationship(
231
    const std::string &name) const
232
0
{
233
0
    const auto oIterRelationships = m_relationships.find(name);
234
0
    if (oIterRelationships != m_relationships.end())
235
0
        return oIterRelationships->second.get();
236
237
0
    const GDALRelationship *poSrcRelationShip = m_srcDS.GetRelationship(name);
238
0
    if (!poSrcRelationShip)
239
0
        return nullptr;
240
0
    const auto oIterLeftTableName =
241
0
        m_mapOldLayerNameToNew.find(poSrcRelationShip->GetLeftTableName());
242
0
    const auto oIterRightTableName =
243
0
        m_mapOldLayerNameToNew.find(poSrcRelationShip->GetRightTableName());
244
0
    const auto oIterMappingTableName =
245
0
        m_mapOldLayerNameToNew.find(poSrcRelationShip->GetMappingTableName());
246
0
    if (oIterLeftTableName == m_mapOldLayerNameToNew.end() &&
247
0
        oIterRightTableName == m_mapOldLayerNameToNew.end() &&
248
0
        oIterMappingTableName == m_mapOldLayerNameToNew.end())
249
0
    {
250
0
        return poSrcRelationShip;
251
0
    }
252
253
0
    auto poNewRelationship =
254
0
        std::make_unique<GDALRelationship>(*poSrcRelationShip);
255
0
    if (oIterLeftTableName != m_mapOldLayerNameToNew.end())
256
0
        poNewRelationship->SetLeftTableName(oIterLeftTableName->second);
257
0
    if (oIterRightTableName != m_mapOldLayerNameToNew.end())
258
0
        poNewRelationship->SetRightTableName(oIterRightTableName->second);
259
0
    if (oIterMappingTableName != m_mapOldLayerNameToNew.end())
260
0
        poNewRelationship->SetMappingTableName(oIterMappingTableName->second);
261
262
0
    return m_relationships.insert({name, std::move(poNewRelationship)})
263
0
        .first->second.get();
264
0
}
265
266
}  // namespace
267
268
/************************************************************************/
269
/*                       TruncateUTF8ToMaxChar()                        */
270
/************************************************************************/
271
272
static void TruncateUTF8ToMaxChar(std::string &osStr, size_t maxCharCount)
273
0
{
274
0
    size_t nCharacterCount = 0;
275
0
    for (size_t i = 0; i < osStr.size(); ++i)
276
0
    {
277
        // Is it first byte of a UTF-8 character?
278
0
        if ((osStr[i] & 0xc0) != 0x80)
279
0
        {
280
0
            ++nCharacterCount;
281
0
            if (nCharacterCount == maxCharCount)
282
0
            {
283
0
                osStr.resize(i + 1);
284
0
                break;
285
0
            }
286
0
        }
287
0
    }
288
0
}
289
290
/************************************************************************/
291
/*              GDALVectorRenameLayerAlgorithm::RunStep()               */
292
/************************************************************************/
293
294
bool GDALVectorRenameLayerAlgorithm::RunStep(GDALPipelineStepRunContext &)
295
0
{
296
0
    auto poSrcDS = m_inputDataset[0].GetDatasetRef();
297
0
    CPLAssert(poSrcDS);
298
299
0
    CPLAssert(m_outputDataset.GetName().empty());
300
0
    CPLAssert(!m_outputDataset.GetDatasetRef());
301
302
    // First pass over layer names to create new layer names matching specified
303
    // constraints
304
0
    std::vector<std::string> aosNames;
305
0
    std::map<std::string, int> oMapCountNames;
306
0
    bool bNonUniqueNames = false;
307
0
    const int nLayerCount = poSrcDS->GetLayerCount();
308
0
    for (int i = 0; i < nLayerCount; ++i)
309
0
    {
310
0
        const OGRLayer *poSrcLayer = poSrcDS->GetLayer(i);
311
0
        if ((m_inputLayerName == poSrcLayer->GetDescription() ||
312
0
             nLayerCount == 1) &&
313
0
            !m_outputLayerName.empty())
314
0
        {
315
0
            aosNames.push_back(m_outputLayerName);
316
0
        }
317
0
        else
318
0
        {
319
0
            std::string osName(poSrcLayer->GetDescription());
320
0
            if (!m_reservedChars.empty())
321
0
            {
322
0
                std::string osNewName;
323
0
                for (char c : osName)
324
0
                {
325
0
                    if (m_reservedChars.find(c) != std::string::npos)
326
0
                    {
327
0
                        if (!m_replacementChar.empty())
328
0
                            osNewName += m_replacementChar;
329
0
                    }
330
0
                    else
331
0
                    {
332
0
                        osNewName += c;
333
0
                    }
334
0
                }
335
0
                osName = std::move(osNewName);
336
0
            }
337
0
            if (m_filenameCompatible)
338
0
            {
339
0
                osName = CPLLaunderForFilenameSafe(
340
0
                    osName, m_replacementChar.c_str()[0]);
341
0
            }
342
0
            if (m_ascii)
343
0
            {
344
0
                char *pszStr = CPLUTF8ForceToASCII(
345
0
                    osName.c_str(), m_replacementChar.c_str()[0]);
346
0
                osName = pszStr;
347
0
                CPLFree(pszStr);
348
0
            }
349
0
            if (m_lowerCase)
350
0
            {
351
0
                for (char &c : osName)
352
0
                {
353
0
                    if (c >= 'A' && c <= 'Z')
354
0
                        c = c - 'A' + 'a';
355
0
                }
356
0
            }
357
0
            if (m_maxLength > 0)
358
0
            {
359
0
                TruncateUTF8ToMaxChar(osName, m_maxLength);
360
0
            }
361
0
            if (++oMapCountNames[osName] > 1)
362
0
                bNonUniqueNames = true;
363
0
            aosNames.push_back(std::move(osName));
364
0
        }
365
0
    }
366
367
    // Extra optional pass if some names are not unique
368
0
    if (bNonUniqueNames)
369
0
    {
370
0
        std::map<std::string, int> oMapCurCounter;
371
0
        bool bUniquenessPossible = true;
372
0
        for (auto &osName : aosNames)
373
0
        {
374
0
            const int nCountForName = oMapCountNames[osName];
375
0
            if (nCountForName > 1)
376
0
            {
377
0
                const int nCounter = ++oMapCurCounter[osName];
378
0
                std::string osSuffix("_");
379
0
                if (nCountForName <= 9)
380
0
                    osSuffix += CPLSPrintf("%d", nCounter);
381
0
                else if (nCountForName <= 99)
382
0
                    osSuffix += CPLSPrintf("%02d", nCounter);
383
0
                else
384
0
                    osSuffix += CPLSPrintf("%03d", nCounter);
385
0
                const size_t nNameLen = CPLStrlenUTF8Ex(osName.c_str());
386
0
                if (m_maxLength > 0 && nNameLen + osSuffix.size() >
387
0
                                           static_cast<size_t>(m_maxLength))
388
0
                {
389
0
                    if (nNameLen > osSuffix.size())
390
0
                    {
391
0
                        TruncateUTF8ToMaxChar(osName,
392
0
                                              nNameLen - osSuffix.size());
393
0
                        osName += osSuffix;
394
0
                    }
395
0
                    else if (bUniquenessPossible)
396
0
                    {
397
0
                        ReportError(CE_Warning, CPLE_AppDefined,
398
0
                                    "Cannot create unique name for '%s' while "
399
0
                                    "respecting %d maximum length",
400
0
                                    osName.c_str(), m_maxLength);
401
0
                        bUniquenessPossible = false;
402
0
                    }
403
0
                }
404
0
                else
405
0
                {
406
0
                    osName += osSuffix;
407
0
                }
408
0
            }
409
0
        }
410
0
    }
411
412
0
    auto outDS = std::make_unique<GDALVectorRenameLayerAlgorithmDataset>(
413
0
        *poSrcDS, aosNames);
414
415
    // Final pass to create output layers
416
0
    for (int i = 0; i < nLayerCount; ++i)
417
0
    {
418
0
        OGRLayer *poSrcLayer = poSrcDS->GetLayer(i);
419
0
        if (poSrcLayer->GetDescription() != aosNames[i])
420
0
        {
421
0
            auto poLayer =
422
0
                std::make_unique<GDALVectorRenameLayerAlgorithmLayer>(
423
0
                    *poSrcLayer, aosNames[i]);
424
0
            outDS->AddLayer(*poSrcLayer, std::move(poLayer));
425
0
        }
426
0
        else
427
0
        {
428
0
            outDS->AddLayer(
429
0
                *poSrcLayer,
430
0
                std::make_unique<GDALVectorPipelinePassthroughLayer>(
431
0
                    *poSrcLayer));
432
0
        }
433
0
    }
434
435
0
    m_outputDataset.Set(std::move(outDS));
436
437
0
    return true;
438
0
}
439
440
GDALVectorRenameLayerAlgorithmStandalone::
441
0
    ~GDALVectorRenameLayerAlgorithmStandalone() = default;
442
443
//! @endcond