Coverage Report

Created: 2026-06-30 08:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/sqlite/ogrsqliteutility.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  SQLite/GeoPackage Translator
4
 * Purpose:  Utility functions for OGR SQLite/GeoPackage driver.
5
 * Author:   Paul Ramsey, pramsey@boundlessgeo.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
9
 * Copyright (c) 2020, Alessandro Pasotti <elpaso@itopen.it>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "cpl_port.h"
15
#include "ogrsqliteutility.h"
16
17
#include <cstdlib>
18
#include <string>
19
#include <iostream>
20
#include <sstream>
21
22
#include "cpl_error.h"
23
#include "ogr_p.h"
24
#include "gdal_priv.h"
25
26
SQLResult::SQLResult(char **result, int nRow, int nCol)
27
12.7k
    : papszResult(result), nRowCount(nRow), nColCount(nCol)
28
12.7k
{
29
12.7k
}
30
31
SQLResult::~SQLResult()
32
12.7k
{
33
12.7k
    if (papszResult)
34
12.7k
    {
35
12.7k
        sqlite3_free_table(papszResult);
36
12.7k
    }
37
12.7k
}
38
39
void SQLResult::LimitRowCount(int nLimit)
40
6
{
41
6
    nRowCount = nLimit;
42
6
}
43
44
/* Runs a SQL command and ignores the result (good for INSERT/UPDATE/CREATE) */
45
OGRErr SQLCommand(sqlite3 *poDb, const char *pszSQL)
46
1.78M
{
47
1.78M
    CPLAssert(poDb != nullptr);
48
1.78M
    CPLAssert(pszSQL != nullptr);
49
50
1.78M
    char *pszErrMsg = nullptr;
51
#ifdef DEBUG_VERBOSE
52
    CPLDebug("GPKG", "exec(%s)", pszSQL);
53
#endif
54
1.78M
    int rc = sqlite3_exec(poDb, pszSQL, nullptr, nullptr, &pszErrMsg);
55
56
1.78M
    if (rc != SQLITE_OK)
57
791
    {
58
791
        CPLError(CE_Failure, CPLE_AppDefined, "sqlite3_exec(%s) failed: %s",
59
791
                 pszSQL, pszErrMsg ? pszErrMsg : "");
60
791
        sqlite3_free(pszErrMsg);
61
791
        return OGRERR_FAILURE;
62
791
    }
63
64
1.78M
    return OGRERR_NONE;
65
1.78M
}
66
67
std::unique_ptr<SQLResult> SQLQuery(sqlite3 *poDb, const char *pszSQL)
68
13.6k
{
69
13.6k
    CPLAssert(poDb != nullptr);
70
13.6k
    CPLAssert(pszSQL != nullptr);
71
72
#ifdef DEBUG_VERBOSE
73
    CPLDebug("GPKG", "get_table(%s)", pszSQL);
74
#endif
75
76
13.6k
    char **papszResult = nullptr;
77
13.6k
    char *pszErrMsg = nullptr;
78
13.6k
    int nRowCount, nColCount;
79
13.6k
    int rc = sqlite3_get_table(poDb, pszSQL, &(papszResult), &(nRowCount),
80
13.6k
                               &(nColCount), &(pszErrMsg));
81
82
13.6k
    if (rc != SQLITE_OK)
83
868
    {
84
868
        CPLError(CE_Failure, CPLE_AppDefined,
85
868
                 "sqlite3_get_table(%s) failed: %s", pszSQL, pszErrMsg);
86
868
        sqlite3_free(pszErrMsg);
87
868
        return nullptr;
88
868
    }
89
90
12.7k
    return std::make_unique<SQLResult>(papszResult, nRowCount, nColCount);
91
13.6k
}
92
93
const char *SQLResult::GetValue(int iColNum, int iRowNum) const
94
1.57M
{
95
1.57M
    const int nCols = nColCount;
96
#ifdef DEBUG
97
    const int nRows = nRowCount;
98
    CPL_IGNORE_RET_VAL(nRows);
99
100
    CPLAssert(iColNum >= 0 && iColNum < nCols);
101
    CPLAssert(iRowNum >= 0 && iRowNum < nRows);
102
#endif
103
1.57M
    return papszResult[nCols + iRowNum * nCols + iColNum];
104
1.57M
}
105
106
int SQLResult::GetValueAsInteger(int iColNum, int iRowNum) const
107
328k
{
108
328k
    const char *pszValue = GetValue(iColNum, iRowNum);
109
328k
    if (!pszValue)
110
124
        return 0;
111
112
328k
    return atoi(pszValue);
113
328k
}
114
115
/* Returns the first row of first column of SQL as integer */
116
GIntBig SQLGetInteger64(sqlite3 *poDb, const char *pszSQL, OGRErr *err)
117
18.7k
{
118
18.7k
    CPLAssert(poDb != nullptr);
119
120
18.7k
    sqlite3_stmt *poStmt = nullptr;
121
122
    /* Prepare the SQL */
123
#ifdef DEBUG_VERBOSE
124
    CPLDebug("GPKG", "get(%s)", pszSQL);
125
#endif
126
18.7k
    int rc = sqlite3_prepare_v2(poDb, pszSQL, -1, &poStmt, nullptr);
127
18.7k
    if (rc != SQLITE_OK)
128
5
    {
129
5
        CPLError(CE_Failure, CPLE_AppDefined,
130
5
                 "sqlite3_prepare_v2(%s) failed: %s", pszSQL,
131
5
                 sqlite3_errmsg(poDb));
132
5
        if (err)
133
0
            *err = OGRERR_FAILURE;
134
5
        return 0;
135
5
    }
136
137
    /* Execute and fetch first row */
138
18.7k
    rc = sqlite3_step(poStmt);
139
18.7k
    if (rc != SQLITE_ROW)
140
5.46k
    {
141
5.46k
        if (err)
142
601
            *err = OGRERR_FAILURE;
143
5.46k
        sqlite3_finalize(poStmt);
144
5.46k
        return 0;
145
5.46k
    }
146
147
    /* Read the integer from the row */
148
13.3k
    GIntBig i = sqlite3_column_int64(poStmt, 0);
149
13.3k
    sqlite3_finalize(poStmt);
150
151
13.3k
    if (err)
152
2.05k
        *err = OGRERR_NONE;
153
13.3k
    return i;
154
18.7k
}
155
156
int SQLGetInteger(sqlite3 *poDb, const char *pszSQL, OGRErr *err)
157
18.7k
{
158
18.7k
    return static_cast<int>(SQLGetInteger64(poDb, pszSQL, err));
159
18.7k
}
160
161
/************************************************************************/
162
/*                            SQLUnescape()                             */
163
/************************************************************************/
164
165
CPLString SQLUnescape(const char *pszVal)
166
199k
{
167
199k
    char chQuoteChar = pszVal[0];
168
199k
    if (chQuoteChar != '\'' && chQuoteChar != '"')
169
199k
        return pszVal;
170
171
64
    CPLString osRet;
172
64
    pszVal++;
173
194
    while (*pszVal != '\0')
174
157
    {
175
157
        if (*pszVal == chQuoteChar)
176
83
        {
177
83
            if (pszVal[1] == chQuoteChar)
178
56
                pszVal++;
179
27
            else
180
27
                break;
181
83
        }
182
130
        osRet += *pszVal;
183
130
        pszVal++;
184
130
    }
185
64
    return osRet;
186
199k
}
187
188
/************************************************************************/
189
/*                          SQLEscapeLiteral()                          */
190
/************************************************************************/
191
192
CPLString SQLEscapeLiteral(const char *pszLiteral)
193
58.3k
{
194
58.3k
    CPLString osVal;
195
1.55M
    for (int i = 0; pszLiteral[i] != '\0'; i++)
196
1.49M
    {
197
1.49M
        if (pszLiteral[i] == '\'')
198
16
            osVal += '\'';
199
1.49M
        osVal += pszLiteral[i];
200
1.49M
    }
201
58.3k
    return osVal;
202
58.3k
}
203
204
/************************************************************************/
205
/*                           SQLEscapeName()                            */
206
/************************************************************************/
207
208
CPLString SQLEscapeName(const char *pszName)
209
1.07M
{
210
1.07M
    CPLString osRet;
211
19.9M
    while (*pszName != '\0')
212
18.9M
    {
213
18.9M
        if (*pszName == '"')
214
27.6k
            osRet += "\"\"";
215
18.8M
        else
216
18.8M
            osRet += *pszName;
217
18.9M
        pszName++;
218
18.9M
    }
219
1.07M
    return osRet;
220
1.07M
}
221
222
/************************************************************************/
223
/*                            SQLTokenize()                             */
224
/************************************************************************/
225
226
char **SQLTokenize(const char *pszStr)
227
0
{
228
0
    char **papszTokens = nullptr;
229
0
    bool bInQuote = false;
230
0
    char chQuoteChar = '\0';
231
0
    bool bInSpace = true;
232
0
    CPLString osCurrentToken;
233
0
    while (*pszStr != '\0')
234
0
    {
235
0
        if (*pszStr == ' ' && !bInQuote)
236
0
        {
237
0
            if (!bInSpace)
238
0
            {
239
0
                papszTokens = CSLAddString(papszTokens, osCurrentToken);
240
0
                osCurrentToken.clear();
241
0
            }
242
0
            bInSpace = true;
243
0
        }
244
0
        else if ((*pszStr == '(' || *pszStr == ')' || *pszStr == ',') &&
245
0
                 !bInQuote)
246
0
        {
247
0
            if (!bInSpace)
248
0
            {
249
0
                papszTokens = CSLAddString(papszTokens, osCurrentToken);
250
0
                osCurrentToken.clear();
251
0
            }
252
0
            osCurrentToken.clear();
253
0
            osCurrentToken += *pszStr;
254
0
            papszTokens = CSLAddString(papszTokens, osCurrentToken);
255
0
            osCurrentToken.clear();
256
0
            bInSpace = true;
257
0
        }
258
0
        else if (*pszStr == '"' || *pszStr == '\'')
259
0
        {
260
0
            if (bInQuote && *pszStr == chQuoteChar && pszStr[1] == chQuoteChar)
261
0
            {
262
0
                osCurrentToken += *pszStr;
263
0
                osCurrentToken += *pszStr;
264
0
                pszStr += 2;
265
0
                continue;
266
0
            }
267
0
            else if (bInQuote && *pszStr == chQuoteChar)
268
0
            {
269
0
                osCurrentToken += *pszStr;
270
0
                papszTokens = CSLAddString(papszTokens, osCurrentToken);
271
0
                osCurrentToken.clear();
272
0
                bInSpace = true;
273
0
                bInQuote = false;
274
0
                chQuoteChar = '\0';
275
0
            }
276
0
            else if (bInQuote)
277
0
            {
278
0
                osCurrentToken += *pszStr;
279
0
            }
280
0
            else
281
0
            {
282
0
                chQuoteChar = *pszStr;
283
0
                osCurrentToken.clear();
284
0
                osCurrentToken += chQuoteChar;
285
0
                bInQuote = true;
286
0
                bInSpace = false;
287
0
            }
288
0
        }
289
0
        else
290
0
        {
291
0
            osCurrentToken += *pszStr;
292
0
            bInSpace = false;
293
0
        }
294
0
        pszStr++;
295
0
    }
296
297
0
    if (!osCurrentToken.empty())
298
0
        papszTokens = CSLAddString(papszTokens, osCurrentToken);
299
300
0
    return papszTokens;
301
0
}
302
303
/************************************************************************/
304
/*                   SQLGetUniqueFieldUCConstraints()                   */
305
/************************************************************************/
306
307
/* Return set of field names (in upper case) that have a UNIQUE constraint,
308
 * only on that single column.
309
 */
310
311
std::set<std::string> SQLGetUniqueFieldUCConstraints(
312
    sqlite3 *poDb, const char *pszTableName,
313
    const std::vector<SQLSqliteMasterContent> &sqliteMasterContent)
314
2.70k
{
315
    // set names (in upper case) of fields with unique constraint
316
2.70k
    std::set<std::string> uniqueFieldsUC;
317
318
    // Unique fields detection
319
2.70k
    const std::string upperTableName{CPLString(pszTableName).toupper()};
320
2.70k
    std::string tableDefinition;
321
322
2.70k
    if (sqliteMasterContent.empty())
323
2.67k
    {
324
2.67k
        char *pszTableDefinitionSQL = sqlite3_mprintf(
325
2.67k
            "SELECT sql, type FROM sqlite_master "
326
2.67k
            "WHERE type IN ('table', 'view') AND UPPER(name)='%q'",
327
2.67k
            upperTableName.c_str());
328
2.67k
        auto oResultTable = SQLQuery(poDb, pszTableDefinitionSQL);
329
2.67k
        sqlite3_free(pszTableDefinitionSQL);
330
331
2.67k
        if (!oResultTable || oResultTable->RowCount() == 0)
332
0
        {
333
0
            if (oResultTable)
334
0
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot find table %s",
335
0
                         pszTableName);
336
337
0
            return uniqueFieldsUC;
338
0
        }
339
2.67k
        if (std::string(oResultTable->GetValue(1, 0)) == "view")
340
1
        {
341
1
            return uniqueFieldsUC;
342
1
        }
343
2.67k
        tableDefinition = oResultTable->GetValue(0, 0);
344
2.67k
    }
345
32
    else
346
32
    {
347
32
        for (const auto &row : sqliteMasterContent)
348
592
        {
349
592
            if (row.osType == "table" &&
350
577
                CPLString(row.osTableName).toupper() == upperTableName)
351
32
            {
352
32
                tableDefinition = row.osSQL;
353
32
                break;
354
32
            }
355
560
            else if (row.osType == "view" &&
356
0
                     CPLString(row.osTableName).toupper() == upperTableName)
357
0
            {
358
0
                return uniqueFieldsUC;
359
0
            }
360
592
        }
361
32
        if (tableDefinition.empty())
362
0
        {
363
0
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot find table %s",
364
0
                     pszTableName);
365
366
0
            return uniqueFieldsUC;
367
0
        }
368
32
    }
369
370
    // Parses strings like "colum_name1" KEYWORD1 KEYWORD2 'some string',
371
    // `column_name2`,"column_name3"
372
2.70k
    const auto GetNextToken =
373
2.70k
        [](const std::string &osStr, size_t &pos, bool keepQuotes)
374
344k
    {
375
344k
        if (pos >= osStr.size())
376
730
            return std::string();
377
343k
        pos = osStr.find_first_not_of(" \t\n\r", pos);
378
343k
        if (pos == std::string::npos)
379
5
            return std::string();
380
381
343k
        std::string osToken;
382
343k
        if (osStr[pos] == '"' || osStr[pos] == '\'' || osStr[pos] == '`')
383
64.6k
        {
384
64.6k
            const char chQuoteChar = osStr[pos];
385
64.6k
            if (keepQuotes)
386
41.6k
                osToken += chQuoteChar;
387
64.6k
            ++pos;
388
3.42M
            while (pos < osStr.size())
389
3.42M
            {
390
3.42M
                if (osStr[pos] == chQuoteChar)
391
67.4k
                {
392
67.4k
                    if (pos + 1 < osStr.size() && osStr[pos + 1] == chQuoteChar)
393
2.95k
                    {
394
2.95k
                        osToken += chQuoteChar;
395
2.95k
                        pos += 2;
396
2.95k
                    }
397
64.4k
                    else
398
64.4k
                    {
399
64.4k
                        if (keepQuotes)
400
41.5k
                            osToken += chQuoteChar;
401
64.4k
                        pos++;
402
64.4k
                        break;
403
64.4k
                    }
404
67.4k
                }
405
3.35M
                else
406
3.35M
                {
407
3.35M
                    osToken += osStr[pos];
408
3.35M
                    pos++;
409
3.35M
                }
410
3.42M
            }
411
64.6k
        }
412
278k
        else if (osStr[pos] == ',' || osStr[pos] == '(' || osStr[pos] == ')')
413
76.9k
        {
414
76.9k
            osToken = osStr[pos];
415
76.9k
            pos++;
416
76.9k
        }
417
201k
        else
418
201k
        {
419
201k
            size_t pos2 = osStr.find_first_of(") \t\n\r,", pos);
420
201k
            if (pos2 == std::string::npos)
421
173
                osToken = osStr.substr(pos);
422
201k
            else
423
201k
                osToken = osStr.substr(pos, pos2 - pos);
424
201k
            pos = pos2;
425
201k
        }
426
343k
        return osToken;
427
343k
    };
428
429
    // Parses CREATE TABLE definition for column UNIQUE keywords
430
2.70k
    {
431
2.70k
        const auto nPosStart = tableDefinition.find('(');
432
2.70k
        const auto nPosEnd = tableDefinition.rfind(')');
433
2.70k
        if (nPosStart != std::string::npos && nPosEnd != std::string::npos &&
434
2.70k
            nPosEnd > nPosStart &&
435
2.70k
            CPLString(tableDefinition).toupper().find("UNIQUE") !=
436
2.70k
                std::string::npos)
437
246
        {
438
246
            tableDefinition =
439
246
                tableDefinition.substr(nPosStart + 1, nPosEnd - nPosStart - 1);
440
246
            size_t pos = 0;
441
246
            bool bHasConstraint = false;
442
54.2k
            while (true)
443
54.2k
            {
444
54.2k
                size_t posBackup = pos;
445
54.2k
                if (EQUAL(
446
54.2k
                        GetNextToken(tableDefinition, posBackup, true).c_str(),
447
54.2k
                        "CONSTRAINT"))
448
0
                {
449
0
                    bHasConstraint = true;
450
0
                    break;
451
0
                }
452
453
54.2k
                const std::string osColName =
454
54.2k
                    GetNextToken(tableDefinition, pos, false);
455
54.2k
                if (osColName.empty())
456
246
                {
457
246
                    break;
458
246
                }
459
235k
                while (true)
460
235k
                {
461
235k
                    const std::string osToken =
462
235k
                        GetNextToken(tableDefinition, pos, true);
463
235k
                    if (osToken.empty() || osToken == ",")
464
54.0k
                        break;
465
181k
                    if (EQUAL(osToken.c_str(), "UNIQUE"))
466
1.92k
                    {
467
1.92k
                        uniqueFieldsUC.insert(CPLString(osColName).toupper());
468
1.92k
                    }
469
181k
                }
470
54.0k
            }
471
472
            // Process https://www.sqlite.org/syntax/table-constraint.html
473
246
            if (bHasConstraint)
474
0
            {
475
0
                while (true)
476
0
                {
477
0
                    if (!EQUAL(GetNextToken(tableDefinition, pos, true).c_str(),
478
0
                               "CONSTRAINT"))
479
0
                    {
480
0
                        break;
481
0
                    }
482
483
0
                    const std::string osConstraintName =
484
0
                        GetNextToken(tableDefinition, pos, false);
485
0
                    if (osConstraintName.empty())
486
0
                    {
487
0
                        break;
488
0
                    }
489
490
0
                    const std::string osConstraintType =
491
0
                        GetNextToken(tableDefinition, pos, true);
492
0
                    if (osConstraintType.empty())
493
0
                    {
494
0
                        break;
495
0
                    }
496
497
0
                    if (EQUAL(osConstraintType.c_str(), "UNIQUE"))
498
0
                    {
499
0
                        std::string osToken =
500
0
                            GetNextToken(tableDefinition, pos, true);
501
0
                        if (osToken != "(")
502
0
                            break;
503
504
0
                        const std::string osColName =
505
0
                            GetNextToken(tableDefinition, pos, false);
506
0
                        osToken = GetNextToken(tableDefinition, pos, true);
507
0
                        if (osToken == ")")
508
0
                        {
509
                            // Only takes into account single column unique constraint
510
0
                            uniqueFieldsUC.insert(
511
0
                                CPLString(osColName).toupper());
512
0
                        }
513
0
                        else
514
0
                        {
515
                            // Skip tokens until ')'
516
0
                            if (!osToken.empty())
517
0
                            {
518
0
                                do
519
0
                                {
520
0
                                    osToken = GetNextToken(tableDefinition, pos,
521
0
                                                           true);
522
0
                                } while (!osToken.empty() && osToken != ")");
523
0
                            }
524
0
                            if (osToken.empty())
525
0
                                break;
526
0
                        }
527
0
                        osToken = GetNextToken(tableDefinition, pos, true);
528
0
                        if (osToken != ",")
529
0
                            break;
530
0
                    }
531
0
                    else
532
0
                    {
533
                        // Skip ignored constraint types by looking for the
534
                        // next "," token, that is not inside parenthesis.
535
0
                        int nCountParenthesis = 0;
536
0
                        std::string osToken;
537
0
                        while (true)
538
0
                        {
539
0
                            osToken = GetNextToken(tableDefinition, pos, true);
540
0
                            if (osToken.empty())
541
0
                                break;
542
0
                            if (nCountParenthesis == 0 && osToken == ",")
543
0
                                break;
544
0
                            else if (osToken == "(")
545
0
                                nCountParenthesis++;
546
0
                            else if (osToken == ")")
547
0
                            {
548
0
                                nCountParenthesis--;
549
0
                                if (nCountParenthesis < 0)
550
0
                                {
551
0
                                    break;
552
0
                                }
553
0
                            }
554
0
                        }
555
0
                        if (!(nCountParenthesis == 0 && osToken == ","))
556
0
                            break;
557
0
                    }
558
0
                }
559
0
            }
560
246
        }
561
2.70k
    }
562
563
    // Search indexes:
564
565
2.70k
    const auto ProcessIndexDefinition =
566
2.70k
        [&uniqueFieldsUC, &GetNextToken](const std::string &indexDefinitionIn)
567
2.70k
    {
568
10
        const auto nPosStart = indexDefinitionIn.find('(');
569
10
        const auto nPosEnd = indexDefinitionIn.rfind(')');
570
10
        if (nPosStart != std::string::npos && nPosEnd != std::string::npos &&
571
10
            nPosEnd > nPosStart)
572
10
        {
573
10
            std::string indexDefinitionMod = indexDefinitionIn.substr(
574
10
                nPosStart + 1, nPosEnd - nPosStart - 1);
575
10
            size_t pos = 0;
576
10
            const std::string osColName =
577
10
                GetNextToken(indexDefinitionMod, pos, false);
578
            // Only matches index on single columns
579
10
            if (GetNextToken(indexDefinitionMod, pos, false).empty())
580
6
            {
581
6
                uniqueFieldsUC.insert(CPLString(osColName).toupper());
582
6
            }
583
10
        }
584
10
    };
585
586
2.70k
    if (sqliteMasterContent.empty())
587
2.67k
    {
588
2.67k
        char *pszTableDefinitionSQL = sqlite3_mprintf(
589
2.67k
            "SELECT sql FROM sqlite_master WHERE type='index' AND"
590
2.67k
            " UPPER(tbl_name)='%q' AND UPPER(sql) "
591
2.67k
            "LIKE 'CREATE UNIQUE INDEX%%'",
592
2.67k
            upperTableName.c_str());
593
2.67k
        auto oResultTable = SQLQuery(poDb, pszTableDefinitionSQL);
594
2.67k
        sqlite3_free(pszTableDefinitionSQL);
595
596
2.67k
        if (!oResultTable)
597
0
        {
598
0
            CPLError(CE_Failure, CPLE_AppDefined,
599
0
                     "Error searching indexes for table %s", pszTableName);
600
0
        }
601
2.67k
        else if (oResultTable->RowCount() >= 0)
602
2.67k
        {
603
2.68k
            for (int rowCnt = 0; rowCnt < oResultTable->RowCount(); ++rowCnt)
604
10
            {
605
10
                std::string indexDefinition{oResultTable->GetValue(0, rowCnt)};
606
10
                ProcessIndexDefinition(indexDefinition);
607
10
            }
608
2.67k
        }
609
2.67k
    }
610
32
    else
611
32
    {
612
32
        for (const auto &row : sqliteMasterContent)
613
698
        {
614
698
            if (row.osType == "index" &&
615
22
                CPLString(row.osTableName).toupper() == upperTableName &&
616
0
                STARTS_WITH_CI(row.osSQL.c_str(), "CREATE UNIQUE INDEX"))
617
0
            {
618
0
                std::string indexDefinition = row.osSQL;
619
0
                ProcessIndexDefinition(indexDefinition);
620
0
            }
621
698
        }
622
32
    }
623
624
2.70k
    return uniqueFieldsUC;
625
2.70k
}
626
627
/************************************************************************/
628
/*               OGRSQLiteRTreeRequiresTrustedSchemaOn()                */
629
/************************************************************************/
630
631
/** Whether the use of a RTree in triggers or views requires trusted_schema
632
 * PRAGMA to be set to ON */
633
bool OGRSQLiteRTreeRequiresTrustedSchemaOn()
634
0
{
635
0
    static bool b = []()
636
0
    {
637
0
        sqlite3 *hDB = nullptr;
638
0
        int rc =
639
0
            sqlite3_open_v2(":memory:", &hDB, SQLITE_OPEN_READWRITE, nullptr);
640
0
        if (rc != SQLITE_OK)
641
0
        {
642
0
            CPLError(CE_Failure, CPLE_AppDefined,
643
0
                     "sqlite3_open_v2(:memory:) failed");
644
0
            sqlite3_close(hDB);
645
0
            return false;
646
0
        }
647
0
        rc = sqlite3_exec(hDB,
648
0
                          "CREATE VIRTUAL TABLE foo_rtree USING rtree(id, "
649
0
                          "minx, miny, maxx, maxy);",
650
0
                          nullptr, nullptr, nullptr);
651
0
        if (rc != SQLITE_OK)
652
0
        {
653
0
            CPLError(CE_Failure, CPLE_AppDefined,
654
0
                     "CREATE VIRTUAL TABLE foo_rtree failed");
655
0
            sqlite3_close(hDB);
656
0
            return false;
657
0
        }
658
0
        rc = sqlite3_exec(hDB, "CREATE VIEW v AS SELECT * FROM foo_rtree;",
659
0
                          nullptr, nullptr, nullptr);
660
0
        if (rc != SQLITE_OK)
661
0
        {
662
0
            CPLError(CE_Failure, CPLE_AppDefined,
663
0
                     "CREATE VIEW v AS SELECT * FROM foo_rtree failed");
664
0
            sqlite3_close(hDB);
665
0
            return false;
666
0
        }
667
        // Try to read the virtual table from a view. As of today (sqlite 3.43.1)
668
        // this require trusted_schema = ON
669
0
        rc = sqlite3_exec(hDB, "SELECT * FROM v", nullptr, nullptr, nullptr);
670
0
        bool bRequiresTrustedSchemaOn = false;
671
0
        if (rc != SQLITE_OK)
672
0
        {
673
0
            CPL_IGNORE_RET_VAL(sqlite3_exec(hDB, "PRAGMA trusted_schema = ON",
674
0
                                            nullptr, nullptr, nullptr));
675
0
            rc =
676
0
                sqlite3_exec(hDB, "SELECT * FROM v", nullptr, nullptr, nullptr);
677
0
            if (rc == SQLITE_OK)
678
0
                bRequiresTrustedSchemaOn = true;
679
0
        }
680
0
        sqlite3_close(hDB);
681
0
        return bRequiresTrustedSchemaOn;
682
0
    }();
683
0
    return b;
684
0
}
685
686
/************************************************************************/
687
/*            OGRSQLiteIsSpatialFunctionReturningGeometry()             */
688
/************************************************************************/
689
690
bool OGRSQLiteIsSpatialFunctionReturningGeometry(const char *pszName)
691
38.1k
{
692
38.1k
    const char *const apszFunctions[] = {
693
38.1k
        "SetSRID(",
694
38.1k
        "IsValidDetail(",
695
38.1k
        "Boundary(",
696
38.1k
        "Envelope(",
697
38.1k
        "ST_Expand(",
698
38.1k
        "ST_Reverse(",
699
38.1k
        "ST_ForceLHR(",
700
38.1k
        "ST_ForcePolygonCW(",
701
38.1k
        "ST_ForcePolygonCCW(",
702
38.1k
        "SanitizeGeometry(",
703
38.1k
        "EnsureClosedRings(",
704
38.1k
        "RemoveRepeatedPoints(",
705
38.1k
        "CastToPoint(",
706
38.1k
        "CastToLinestring(",
707
38.1k
        "CastToPolygon(",
708
38.1k
        "CastToMultiPoint(",
709
38.1k
        "CastToMultiLinestring(",
710
38.1k
        "CastToMultiPolygon(",
711
38.1k
        "CastToGeometryCollection(",
712
38.1k
        "CastToMulti(",
713
38.1k
        "ST_Multi(",
714
38.1k
        "CastToSingle(",
715
38.1k
        "CastToXY(",
716
38.1k
        "CastToXYZ(",
717
38.1k
        "CastToXYM(",
718
38.1k
        "CastToXYZM(",
719
38.1k
        "StartPoint(",
720
38.1k
        "ST_EndPoint(",
721
38.1k
        "PointOnSurface(",
722
38.1k
        "Simplify(",
723
38.1k
        "ST_Generalize(",
724
38.1k
        "SimplifyPreserveTopology(",
725
38.1k
        "PointN(",
726
38.1k
        "AddPoint(",
727
38.1k
        "SetPoint(",
728
38.1k
        "SetStartPoint(",
729
38.1k
        "SetEndPoint(",
730
38.1k
        "RemovePoint(",
731
38.1k
        "Centroid(",
732
38.1k
        "ExteriorRing(",
733
38.1k
        "InteriorRingN(",
734
38.1k
        "GeometryN(",
735
38.1k
        "ST_AddMeasure(",
736
38.1k
        "ST_Locate_Along_Measure(",
737
38.1k
        "ST_LocateAlong(",
738
38.1k
        "ST_Locate_Between_Measures(",
739
38.1k
        "ST_LocateBetween(",
740
38.1k
        "ST_TrajectoryInterpolarePoint(",
741
38.1k
        "Intersection(",
742
38.1k
        "Difference(",
743
38.1k
        "GUnion(",
744
38.1k
        "ST_Union(",  // UNION is not a valid function name
745
38.1k
        "SymDifference(",
746
38.1k
        "Buffer(",
747
38.1k
        "ConvexHull(",
748
38.1k
        "OffsetCurve(",
749
38.1k
        "SingleSidedBuffer(",
750
38.1k
        "SharedPaths(",
751
38.1k
        "Line_Interpolate_Point(",
752
38.1k
        "Line_Interpolate_Equidistant_Points(",
753
38.1k
        "Line_Substring(",
754
38.1k
        "ClosestPoint(",
755
38.1k
        "ShortestLine(",
756
38.1k
        "Snap(",
757
38.1k
        "Collect(",
758
38.1k
        "LineMerge(",
759
38.1k
        "BuildArea(",
760
38.1k
        "Polygonize(",
761
38.1k
        "MakePolygon(",
762
38.1k
        "UnaryUnion(",
763
38.1k
        "UnaryUnion(",
764
38.1k
        "DrapeLine(",
765
38.1k
        "DrapeLineExceptions(",
766
38.1k
        "DissolveSegments(",
767
38.1k
        "DissolvePoints(",
768
38.1k
        "LinesFromRings(",
769
38.1k
        "LinesCutAtNodes(",
770
38.1k
        "RingsCutAtNodes(",
771
38.1k
        "CollectionExtract(",
772
38.1k
        "ExtractMultiPoint(",
773
38.1k
        "ExtractMultiLinestring(",
774
38.1k
        "ExtractMultiPolygon(",
775
38.1k
        "DelaunayTriangulation(",
776
38.1k
        "VoronojDiagram(",
777
38.1k
        "ConcaveHull(",
778
38.1k
        "MakeValid(",
779
38.1k
        "MakeValidDiscarded(",
780
38.1k
        "Segmentize(",
781
38.1k
        "Split(",
782
38.1k
        "SplitLeft(",
783
38.1k
        "SplitRight(",
784
38.1k
        "SnapAndSplit(",
785
38.1k
        "Project(",
786
38.1k
        "SnapToGrid(",
787
38.1k
        "ST_Node(",
788
38.1k
        "SelfIntersections(",
789
38.1k
        "ST_Subdivide(",
790
38.1k
        "Transform(",
791
38.1k
        "TransformXY(",
792
38.1k
        "TransformXYZ(",
793
38.1k
        "ShiftCoords(",
794
38.1k
        "ShiftCoordinates(",
795
38.1k
        "ST_Translate(",
796
38.1k
        "ST_Shift_Longitude(",
797
38.1k
        "NormalizeLonLat(",
798
38.1k
        "ScaleCoords(",
799
38.1k
        "ScaleCoordinates(",
800
38.1k
        "RotateCoords(",
801
38.1k
        "RotateCoordinates(",
802
38.1k
        "ReflectCoords(",
803
38.1k
        "ReflectCoordinates(",
804
38.1k
        "SwapCoords(",
805
38.1k
        "SwapCoordinates(",
806
38.1k
        "ATM_Transform(",
807
38.1k
        "gpkgMakePoint(",
808
38.1k
        "gpkgMakePointZ(",
809
38.1k
        "gpkgMakePointZM(",
810
38.1k
        "gpkgMakePointM(",
811
38.1k
        "AsGPB(",
812
38.1k
        "GeomFromGPB(",
813
38.1k
        "CastAutomagic(",
814
38.1k
    };
815
38.1k
    for (const char *pszFunction : apszFunctions)
816
4.61M
    {
817
4.61M
        if (STARTS_WITH_CI(pszName, pszFunction) ||
818
4.61M
            (!STARTS_WITH_CI(pszFunction, "ST_") &&
819
4.61M
             STARTS_WITH_CI(pszName, "ST_") &&
820
4.61M
             STARTS_WITH_CI(pszName + strlen("ST_"), pszFunction)))
821
0
        {
822
0
            return true;
823
0
        }
824
4.61M
    }
825
38.1k
    return false;
826
38.1k
}
827
828
double SQLResult::GetValueAsDouble(int iColNum, int iRowNum) const
829
0
{
830
0
    const char *pszValue = GetValue(iColNum, iRowNum);
831
0
    if (!pszValue)
832
0
        return 0;
833
834
0
    return CPLStrtod(pszValue, nullptr);
835
0
}
836
837
/************************************************************************/
838
/*               OGRSQLite_gdal_get_pixel_value_common()                */
839
/************************************************************************/
840
841
void OGRSQLite_gdal_get_pixel_value_common(const char *pszFunctionName,
842
                                           sqlite3_context *pContext, int argc,
843
                                           sqlite3_value **argv,
844
                                           GDALDataset *poDS)
845
0
{
846
0
    if (sqlite3_value_type(argv[1]) != SQLITE_INTEGER ||
847
0
        sqlite3_value_type(argv[2]) != SQLITE_TEXT ||
848
0
        (sqlite3_value_type(argv[3]) != SQLITE_INTEGER &&
849
0
         sqlite3_value_type(argv[3]) != SQLITE_FLOAT) ||
850
0
        (sqlite3_value_type(argv[4]) != SQLITE_INTEGER &&
851
0
         sqlite3_value_type(argv[4]) != SQLITE_FLOAT) ||
852
0
        (argc == 6 && sqlite3_value_type(argv[5]) != SQLITE_TEXT))
853
0
    {
854
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid arguments to %s()",
855
0
                 pszFunctionName);
856
0
        sqlite3_result_null(pContext);
857
0
        return;
858
0
    }
859
860
0
    const int nBand = sqlite3_value_int(argv[1]);
861
0
    auto poBand = poDS->GetRasterBand(nBand);
862
0
    if (!poBand)
863
0
    {
864
0
        sqlite3_result_null(pContext);
865
0
        return;
866
0
    }
867
868
0
    const char *pszCoordType =
869
0
        reinterpret_cast<const char *>(sqlite3_value_text(argv[2]));
870
0
    double x, y;
871
0
    if (EQUAL(pszCoordType, "georef"))
872
0
    {
873
0
        const double X = sqlite3_value_double(argv[3]);
874
0
        const double Y = sqlite3_value_double(argv[4]);
875
0
        GDALGeoTransform gt;
876
0
        if (poDS->GetGeoTransform(gt) != CE_None)
877
0
        {
878
0
            sqlite3_result_null(pContext);
879
0
            return;
880
0
        }
881
0
        GDALGeoTransform invGT;
882
0
        if (!gt.GetInverse(invGT))
883
0
        {
884
0
            sqlite3_result_null(pContext);
885
0
            return;
886
0
        }
887
0
        x = invGT[0] + X * invGT[1] + Y * invGT[2];
888
0
        y = invGT[3] + X * invGT[4] + Y * invGT[5];
889
0
    }
890
0
    else if (EQUAL(pszCoordType, "pixel"))
891
0
    {
892
0
        x = sqlite3_value_int(argv[3]);
893
0
        y = sqlite3_value_int(argv[4]);
894
0
    }
895
0
    else
896
0
    {
897
0
        CPLError(CE_Failure, CPLE_AppDefined,
898
0
                 "Invalid value for 3rd argument of gdal_get_pixel_value(): "
899
0
                 "only 'georef' or 'pixel' are supported");
900
0
        sqlite3_result_null(pContext);
901
0
        return;
902
0
    }
903
0
    if (x < 0 || x >= poDS->GetRasterXSize() || y < 0 ||
904
0
        y >= poDS->GetRasterYSize())
905
0
    {
906
0
        sqlite3_result_null(pContext);
907
0
        return;
908
0
    }
909
910
0
    const auto eInterpolation =
911
0
        argc == 6 ? GDALRasterIOGetResampleAlg(reinterpret_cast<const char *>(
912
0
                        sqlite3_value_text(argv[5])))
913
0
                  : GRIORA_NearestNeighbour;
914
915
0
    const auto eDT = poBand->GetRasterDataType();
916
0
    if (eDT == GDT_Int64 && eInterpolation == GRIORA_NearestNeighbour)
917
0
    {
918
0
        int64_t nValue = 0;
919
0
        if (poBand->RasterIO(GF_Read, static_cast<int>(x), static_cast<int>(y),
920
0
                             1, 1, &nValue, 1, 1, GDT_Int64, 0, 0,
921
0
                             nullptr) != CE_None)
922
0
        {
923
0
            sqlite3_result_null(pContext);
924
0
            return;
925
0
        }
926
0
        return sqlite3_result_int64(pContext, nValue);
927
0
    }
928
0
    else if (eDT == GDT_UInt64 && eInterpolation == GRIORA_NearestNeighbour)
929
0
    {
930
0
        uint64_t nValue = 0;
931
0
        if (poBand->RasterIO(GF_Read, static_cast<int>(x), static_cast<int>(y),
932
0
                             1, 1, &nValue, 1, 1, GDT_UInt64, 0, 0,
933
0
                             nullptr) != CE_None)
934
0
        {
935
0
            sqlite3_result_null(pContext);
936
0
            return;
937
0
        }
938
0
        if (nValue > static_cast<uint64_t>(INT64_MAX))
939
0
        {
940
            // Not ideal
941
0
            return sqlite3_result_double(pContext, static_cast<double>(nValue));
942
0
        }
943
0
        else
944
0
        {
945
0
            return sqlite3_result_int64(pContext, static_cast<int64_t>(nValue));
946
0
        }
947
0
    }
948
0
    else
949
0
    {
950
0
        double dfValue = 0;
951
0
        if (poBand->InterpolateAtPoint(x, y, eInterpolation, &dfValue,
952
0
                                       nullptr) != CE_None)
953
0
        {
954
0
            sqlite3_result_null(pContext);
955
0
            return;
956
0
        }
957
0
        return sqlite3_result_double(pContext, dfValue);
958
0
    }
959
0
}
960
961
#if defined(DEBUG) || defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION) ||     \
962
    defined(ALLOW_FORMAT_DUMPS)
963
964
/************************************************************************/
965
/*                         SQLCheckLineIsSafe()                         */
966
/************************************************************************/
967
968
bool SQLCheckLineIsSafe(const char *pszLine)
969
3.40M
{
970
3.40M
    CPLString osLine;
971
    // Strip identifiers and string literals from line
972
3.40M
    char chStringEnd = 0;
973
103M
    for (size_t i = 0; pszLine[i]; ++i)
974
100M
    {
975
100M
        if (chStringEnd)
976
46.0M
        {
977
46.0M
            if (pszLine[i] == chStringEnd && pszLine[i + 1] == chStringEnd)
978
71.3k
                ++i;
979
45.9M
            else if (pszLine[i] == chStringEnd)
980
1.02M
            {
981
1.02M
                osLine += chStringEnd;
982
1.02M
                chStringEnd = 0;
983
1.02M
            }
984
46.0M
        }
985
54.1M
        else if (pszLine[i] == '\'' || pszLine[i] == '"')
986
1.23M
            chStringEnd = pszLine[i];
987
52.9M
        else
988
52.9M
            osLine += chStringEnd;
989
100M
    }
990
3.40M
    osLine.replaceAll("replace(", 'x');
991
992
    // Reject a few words tat might have security implications
993
    // Basically we just want to allow CREATE TABLE and INSERT INTO
994
3.40M
    if (osLine.ifind("ATTACH") != std::string::npos ||
995
3.40M
        osLine.ifind("DETACH") != std::string::npos ||
996
3.40M
        osLine.ifind("PRAGMA") != std::string::npos ||
997
3.40M
        osLine.ifind("SELECT") != std::string::npos ||
998
3.40M
        osLine.ifind("UPDATE") != std::string::npos ||
999
3.40M
        osLine.ifind("REPLACE") != std::string::npos ||
1000
3.40M
        osLine.ifind("DELETE") != std::string::npos ||
1001
3.40M
        osLine.ifind("DROP") != std::string::npos ||
1002
3.40M
        osLine.ifind("ALTER") != std::string::npos ||
1003
3.40M
        osLine.ifind("VIRTUAL") != std::string::npos)
1004
0
    {
1005
0
        bool bOK = false;
1006
        // Accept creation of spatial index
1007
0
        if (STARTS_WITH_CI(pszLine, "CREATE VIRTUAL TABLE "))
1008
0
        {
1009
0
            const char *pszStr = pszLine + strlen("CREATE VIRTUAL TABLE ");
1010
0
            if (*pszStr == '"')
1011
0
                pszStr++;
1012
0
            while ((*pszStr >= 'a' && *pszStr <= 'z') ||
1013
0
                   (*pszStr >= 'A' && *pszStr <= 'Z') || *pszStr == '_')
1014
0
            {
1015
0
                pszStr++;
1016
0
            }
1017
0
            if (*pszStr == '"')
1018
0
                pszStr++;
1019
0
            if (EQUAL(pszStr, " USING rtree(id, minx, maxx, miny, maxy);"))
1020
0
            {
1021
0
                bOK = true;
1022
0
            }
1023
0
        }
1024
        // Accept INSERT INTO rtree_poly_geom SELECT fid, ST_MinX(geom),
1025
        // ST_MaxX(geom), ST_MinY(geom), ST_MaxY(geom) FROM poly;
1026
0
        else if (STARTS_WITH_CI(pszLine, "INSERT INTO rtree_") &&
1027
0
                 CPLString(pszLine).ifind("SELECT") != std::string::npos)
1028
0
        {
1029
0
            const CPLStringList aosTokens(
1030
0
                CSLTokenizeString2(pszLine, " (),,", 0));
1031
0
            if (aosTokens.size() == 15 && EQUAL(aosTokens[3], "SELECT") &&
1032
0
                EQUAL(aosTokens[5], "ST_MinX") &&
1033
0
                EQUAL(aosTokens[7], "ST_MaxX") &&
1034
0
                EQUAL(aosTokens[9], "ST_MinY") &&
1035
0
                EQUAL(aosTokens[11], "ST_MaxY") && EQUAL(aosTokens[13], "FROM"))
1036
0
            {
1037
0
                bOK = true;
1038
0
            }
1039
0
        }
1040
1041
0
        if (!bOK)
1042
0
        {
1043
0
            CPLError(CE_Failure, CPLE_NotSupported, "Rejected statement: %s",
1044
0
                     pszLine);
1045
0
            return false;
1046
0
        }
1047
0
    }
1048
3.40M
    return true;
1049
3.40M
}
1050
1051
#endif
1052
1053
int SQLPrepareWithError(sqlite3 *db, const char *sql, int nByte,
1054
                        sqlite3_stmt **ppStmt, const char **pzTail)
1055
6.07k
{
1056
6.07k
    int ret = sqlite3_prepare_v2(db, sql, nByte, ppStmt, pzTail);
1057
6.07k
    if (ret != SQLITE_OK)
1058
471
    {
1059
471
        CPLError(CE_Failure, CPLE_AppDefined, "Failed to prepare SQL %s: %s",
1060
471
                 sql, sqlite3_errmsg(db));
1061
471
    }
1062
6.07k
    return ret;
1063
6.07k
}
1064
1065
/** Returns true if pszTail (has set by sqlite3_prepare_v2) has still
1066
 * remaining non-blank / non-comment content.
1067
 */
1068
bool SQLHasRemainingContent(const char *pszTail)
1069
0
{
1070
0
    bool bInCComment = false;
1071
0
    bool bInSingleLineComment = false;
1072
0
    for (; *pszTail; ++pszTail)
1073
0
    {
1074
0
        const char c = *pszTail;
1075
0
        const char cNext = pszTail[1];
1076
0
        if (c == ' ' || c == '\t')
1077
0
        {
1078
0
            continue;
1079
0
        }
1080
0
        else if (c == '\n' || c == '\r')
1081
0
        {
1082
0
            bInSingleLineComment = false;
1083
0
        }
1084
0
        else if (c == '-' && cNext == '-')
1085
0
        {
1086
0
            bInSingleLineComment = true;
1087
0
        }
1088
0
        else if (c == '/' && cNext == '*')
1089
0
        {
1090
0
            bInCComment = true;
1091
0
        }
1092
0
        else if (bInCComment && c == '*' && cNext == '/')
1093
0
        {
1094
0
            bInCComment = false;
1095
0
        }
1096
0
        else if (!bInCComment && !bInSingleLineComment)
1097
0
        {
1098
0
            CPLError(CE_Failure, CPLE_NotSupported,
1099
0
                     "Multiple statements are not supported");
1100
0
            return true;
1101
0
        }
1102
0
    }
1103
0
    return false;
1104
0
}
1105
1106
/************************************************************************/
1107
/*                   SQLFormatErrorMsgFailedPrepare()                   */
1108
/************************************************************************/
1109
1110
std::string SQLFormatErrorMsgFailedPrepare(sqlite3 *hDB,
1111
                                           const char *pszErrMsgIntro,
1112
                                           const char *pszSQL)
1113
409
{
1114
409
    std::string osErrorMsg(pszErrMsgIntro);
1115
409
    osErrorMsg += sqlite3_errmsg(hDB);
1116
409
    osErrorMsg += "\n  ";
1117
409
    osErrorMsg += pszSQL;
1118
409
#if SQLITE_VERSION_NUMBER >= 3038000L
1119
409
    const int nOffset = sqlite3_error_offset(hDB);
1120
409
    if (nOffset >= 0 && static_cast<size_t>(nOffset) <= strlen(pszSQL))
1121
409
    {
1122
409
        osErrorMsg += "\n  ";
1123
409
        osErrorMsg.append(nOffset, ' ');
1124
409
        osErrorMsg += "^--- error here";
1125
409
    }
1126
409
#endif
1127
409
    return osErrorMsg;
1128
409
}
1129
1130
/************************************************************************/
1131
/*                       SQLGetSQLite3DataType()                        */
1132
/************************************************************************/
1133
1134
const char *SQLGetSQLite3DataType(int nSQLite3DataType)
1135
2
{
1136
2
    switch (nSQLite3DataType)
1137
2
    {
1138
1
        case SQLITE_NULL:
1139
1
            return "NULL";
1140
1
        case SQLITE_TEXT:
1141
1
            return "TEXT";
1142
0
        case SQLITE_INTEGER:
1143
0
            return "INTEGER";
1144
0
        case SQLITE_FLOAT:
1145
0
            return "FLOAT";
1146
0
        case SQLITE_BLOB:
1147
0
            return "BLOB";
1148
2
    }
1149
0
    return "(unknown)";
1150
2
}