Coverage Report

Created: 2025-12-31 08:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/adbc/ogradbcdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Arrow Database Connectivity driver
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2024, Even Rouault <even dot rouault at spatialys.com>
9
 * Copyright (c) 2024, Dewey Dunnington <dewey@voltrondata.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "ogr_adbc.h"
15
#include "ogradbcdrivercore.h"
16
#include "memdataset.h"
17
#include "ogr_p.h"
18
#include "cpl_error.h"
19
#include "cpl_json.h"
20
#include "gdal_adbc.h"
21
22
#include <algorithm>
23
24
#if defined(OGR_ADBC_HAS_DRIVER_MANAGER)
25
#ifdef __clang__
26
#pragma clang diagnostic push
27
#pragma clang diagnostic ignored "-Wdocumentation"
28
#endif
29
#include <arrow-adbc/adbc_driver_manager.h>
30
#ifdef __clang__
31
#pragma clang diagnostic pop
32
#endif
33
#endif
34
35
// #define DEBUG_VERBOSE
36
37
0
#define OGR_ADBC_VERSION ADBC_VERSION_1_1_0
38
static_assert(sizeof(AdbcDriver) == ADBC_DRIVER_1_1_0_SIZE);
39
40
namespace
41
{
42
43
#if !defined(OGR_ADBC_HAS_DRIVER_MANAGER)
44
AdbcStatusCode OGRDuckDBLoadDriver(const char *driver_name, void *driver,
45
                                   struct AdbcError *error)
46
0
{
47
0
    void *load_handle = CPLGetSymbol(driver_name, "duckdb_adbc_init");
48
0
    if (!load_handle)
49
0
    {
50
0
        return ADBC_STATUS_INTERNAL;
51
0
    }
52
53
0
    AdbcDriverInitFunc init_func =
54
0
        reinterpret_cast<AdbcDriverInitFunc>(load_handle);
55
0
    return init_func(OGR_ADBC_VERSION, driver, error);
56
0
}
57
#endif
58
59
AdbcStatusCode OGRADBCLoadDriver(const char *driver_name,
60
                                 const char *entrypoint, void *driver,
61
                                 struct AdbcError *error)
62
0
{
63
0
    GDALAdbcLoadDriverFunc load_driver_override =
64
0
        GDALGetAdbcLoadDriverOverride();
65
0
    if (load_driver_override)
66
0
    {
67
0
        return load_driver_override(driver_name, entrypoint, OGR_ADBC_VERSION,
68
0
                                    driver, error);
69
0
    }
70
0
    else
71
0
    {
72
#if defined(OGR_ADBC_HAS_DRIVER_MANAGER)
73
        return AdbcLoadDriver(driver_name, entrypoint, OGR_ADBC_VERSION, driver,
74
                              error);
75
#else
76
        // If the driver is for DuckDB, use a minimal loading function, which
77
        // doesn't rely on the ADBC driver manager.
78
0
        if (strstr(driver_name, "duckdb"))
79
0
        {
80
0
            return OGRDuckDBLoadDriver(driver_name, driver, error);
81
0
        }
82
0
        return ADBC_STATUS_NOT_IMPLEMENTED;
83
0
#endif
84
0
    }
85
0
}
86
87
}  // namespace
88
89
// Helper to wrap driver callbacks
90
0
#define ADBC_CALL(func, ...) m_driver.func(__VA_ARGS__)
91
92
/************************************************************************/
93
/*                           ~OGRADBCDataset()                          */
94
/************************************************************************/
95
96
OGRADBCDataset::~OGRADBCDataset()
97
29
{
98
29
    OGRADBCDataset::FlushCache(true);
99
100
    // Layers must be closed before the connection
101
29
    m_apoLayers.clear();
102
29
    OGRADBCError error;
103
29
    if (m_connection)
104
0
        ADBC_CALL(ConnectionRelease, m_connection.get(), error);
105
29
    error.clear();
106
29
    if (m_driver.release)
107
0
    {
108
0
        ADBC_CALL(DatabaseRelease, &m_database, error);
109
0
        m_driver.release(&m_driver, error);
110
0
    }
111
29
}
112
113
/************************************************************************/
114
/*                           FlushCache()                               */
115
/************************************************************************/
116
117
CPLErr OGRADBCDataset::FlushCache(bool /* bAtClosing */)
118
29
{
119
29
    CPLErr eErr = CE_None;
120
29
    for (auto &poLayer : m_apoLayers)
121
0
    {
122
0
        auto poADBCLayer = dynamic_cast<OGRADBCLayer *>(poLayer.get());
123
0
        if (poADBCLayer)
124
0
        {
125
0
            if (!poADBCLayer->RunDeferredCreation())
126
0
                eErr = CE_Failure;
127
0
        }
128
0
    }
129
130
29
    return eErr;
131
29
}
132
133
/************************************************************************/
134
/*                           CreateLayer()                              */
135
/************************************************************************/
136
137
std::unique_ptr<OGRADBCLayer>
138
OGRADBCDataset::CreateLayer(const char *pszStatement, const char *pszLayerName,
139
                            bool bInternalUse)
140
0
{
141
142
0
    CPLString osStatement(pszStatement);
143
0
    if (!m_osParquetFilename.empty())
144
0
    {
145
0
        const char *pszSrcLayerName = m_apoLayers.size() == 1
146
0
                                          ? m_apoLayers[0]->GetDescription()
147
0
                                          : pszLayerName;
148
        // Substitute the OGR layer name with the DuckDB expected filename,
149
        // single-quoted
150
0
        const std::string osFrom =
151
0
            std::string(" FROM ").append(pszSrcLayerName);
152
0
        const auto nPos = osStatement.ifind(osFrom);
153
0
        if (nPos != std::string::npos)
154
0
        {
155
0
            osStatement =
156
0
                osStatement.substr(0, nPos)
157
0
                    .append(" FROM '")
158
0
                    .append(OGRDuplicateCharacter(m_osParquetFilename, '\''))
159
0
                    .append("'")
160
0
                    .append(osStatement.substr(nPos + osFrom.size()));
161
0
        }
162
0
        else
163
0
        {
164
0
            const std::string osFrom2 =
165
0
                std::string(" FROM \"")
166
0
                    .append(OGRDuplicateCharacter(pszSrcLayerName, '"'))
167
0
                    .append("\"");
168
0
            const auto nPos2 = osStatement.ifind(osFrom2);
169
0
            if (nPos2 != std::string::npos)
170
0
            {
171
0
                osStatement =
172
0
                    osStatement.substr(0, nPos2)
173
0
                        .append(" FROM '")
174
0
                        .append(
175
0
                            OGRDuplicateCharacter(m_osParquetFilename, '\''))
176
0
                        .append("'")
177
0
                        .append(osStatement.substr(nPos2 + osFrom2.size()));
178
0
            }
179
0
        }
180
0
    }
181
182
0
    return std::make_unique<OGRADBCLayer>(this, pszLayerName, osStatement,
183
0
                                          bInternalUse);
184
0
}
185
186
/************************************************************************/
187
/*                        CreateInternalLayer()                         */
188
/************************************************************************/
189
190
std::unique_ptr<OGRADBCLayer>
191
OGRADBCDataset::CreateInternalLayer(const char *pszStatement)
192
0
{
193
#ifdef DEBUG_VERBOSE
194
    CPLDebug("ADBC", "%s", pszStatement);
195
#endif
196
0
    return CreateLayer(pszStatement, "temp", true);
197
0
}
198
199
/************************************************************************/
200
/*                             ExecuteSQL()                             */
201
/************************************************************************/
202
203
OGRLayer *OGRADBCDataset::ExecuteSQL(const char *pszStatement,
204
                                     OGRGeometry *poSpatialFilter,
205
                                     const char *pszDialect)
206
0
{
207
0
    for (auto &poLayer : m_apoLayers)
208
0
    {
209
0
        auto poADBCLayer = dynamic_cast<OGRADBCLayer *>(poLayer.get());
210
0
        if (poADBCLayer)
211
0
            poADBCLayer->RunDeferredCreation();
212
0
    }
213
214
0
    if (pszDialect && pszDialect[0] != 0 && !EQUAL(pszDialect, "NATIVE"))
215
0
    {
216
0
        return GDALDataset::ExecuteSQL(pszStatement, poSpatialFilter,
217
0
                                       pszDialect);
218
0
    }
219
220
0
    std::string osStatement(pszStatement);
221
0
    for (const char *pszPrefix : {"SELECT * FROM ", "SELECT COUNT(*) FROM "})
222
0
    {
223
0
        if (m_bIsBigQuery && STARTS_WITH_CI(pszStatement, pszPrefix))
224
0
        {
225
0
            const auto nPos = osStatement.find(' ', strlen(pszPrefix));
226
0
            const std::string osTableName = osStatement.substr(
227
0
                strlen(pszPrefix),
228
0
                nPos == std::string::npos ? nPos : nPos - strlen(pszPrefix));
229
0
            auto poADBCLayer = dynamic_cast<OGRADBCBigQueryLayer *>(
230
0
                GetLayerByName(osTableName.c_str()));
231
0
            if (poADBCLayer)
232
0
            {
233
0
                std::string osDatasetId;
234
0
                std::string osTableId;
235
0
                if (poADBCLayer->GetBigQueryDatasetAndTableId(osDatasetId,
236
0
                                                              osTableId))
237
0
                {
238
0
                    std::string osNewStatement = pszPrefix;
239
0
                    osNewStatement += '`';
240
0
                    osNewStatement += OGRDuplicateCharacter(osDatasetId, '`');
241
0
                    osNewStatement += "`.`";
242
0
                    osNewStatement += OGRDuplicateCharacter(osTableId, '`');
243
0
                    osNewStatement += '`';
244
0
                    if (nPos != std::string::npos)
245
0
                        osNewStatement += osStatement.substr(nPos);
246
0
                    osStatement = std::move(osNewStatement);
247
0
                }
248
0
            }
249
0
            break;
250
0
        }
251
0
    }
252
253
0
    const char *pszLayerName = "RESULTSET";
254
0
    std::unique_ptr<OGRADBCLayer> poLayer;
255
0
    if (m_bIsBigQuery)
256
0
        poLayer = std::make_unique<OGRADBCBigQueryLayer>(
257
0
            this, pszLayerName, osStatement,
258
0
            /* bInternalUse = */ false);
259
0
    else
260
0
        poLayer = CreateLayer(osStatement.c_str(), pszLayerName, false);
261
0
    if (poLayer->GotError())
262
0
        return nullptr;
263
0
    if (poSpatialFilter)
264
0
    {
265
0
        if (poLayer->GetGeomType() == wkbNone)
266
0
            return nullptr;
267
0
        poLayer->SetSpatialFilter(poSpatialFilter);
268
0
    }
269
0
    return poLayer.release();
270
0
}
271
272
/************************************************************************/
273
/*                       IsParquetExtension()                           */
274
/************************************************************************/
275
276
static bool IsParquetExtension(const char *pszStr)
277
29
{
278
29
    const std::string osExt = CPLGetExtensionSafe(pszStr);
279
29
    return EQUAL(osExt.c_str(), "parquet") || EQUAL(osExt.c_str(), "parq");
280
29
}
281
282
/************************************************************************/
283
/*                                Open()                                */
284
/************************************************************************/
285
286
bool OGRADBCDataset::Open(const GDALOpenInfo *poOpenInfo)
287
29
{
288
29
    OGRADBCError error;
289
290
29
    const char *pszFilename = poOpenInfo->pszFilename;
291
29
    std::unique_ptr<GDALOpenInfo> poTmpOpenInfo;
292
29
    if (STARTS_WITH(pszFilename, "ADBC:"))
293
29
    {
294
29
        pszFilename += strlen("ADBC:");
295
29
        if (pszFilename[0])
296
29
        {
297
29
            poTmpOpenInfo = std::make_unique<GDALOpenInfo>(pszFilename,
298
29
                                                           poOpenInfo->eAccess);
299
29
            poTmpOpenInfo->papszOpenOptions = poOpenInfo->papszOpenOptions;
300
29
            poOpenInfo = poTmpOpenInfo.get();
301
29
        }
302
29
    }
303
29
    const char *pszADBCDriverName =
304
29
        CSLFetchNameValue(poOpenInfo->papszOpenOptions, "ADBC_DRIVER");
305
29
    m_bIsDuckDBDataset = OGRADBCDriverIsDuckDB(poOpenInfo);
306
29
    const bool bIsSQLite3 =
307
29
        (pszADBCDriverName && EQUAL(pszADBCDriverName, "adbc_driver_sqlite")) ||
308
29
        OGRADBCDriverIsSQLite3(poOpenInfo);
309
29
    bool bIsParquet =
310
29
        OGRADBCDriverIsParquet(poOpenInfo) || IsParquetExtension(pszFilename);
311
29
    m_bIsBigQuery =
312
29
        pszADBCDriverName && strstr(pszADBCDriverName, "adbc_driver_bigquery");
313
29
    const char *pszSQL = CSLFetchNameValue(poOpenInfo->papszOpenOptions, "SQL");
314
29
    if (!bIsParquet && pszSQL)
315
0
    {
316
0
        CPLString osSQL(pszSQL);
317
0
        auto iPos = osSQL.find("FROM '");
318
0
        if (iPos != std::string::npos)
319
0
        {
320
0
            iPos += strlen("FROM '");
321
0
            const auto iPos2 = osSQL.find("'", iPos);
322
0
            if (iPos2 != std::string::npos)
323
0
            {
324
0
                std::string osFilename = osSQL.substr(iPos, iPos2 - iPos);
325
0
                if (IsParquetExtension(osFilename.c_str()))
326
0
                {
327
0
                    m_osParquetFilename = std::move(osFilename);
328
0
                    bIsParquet = true;
329
0
                }
330
0
            }
331
0
        }
332
0
    }
333
29
    const bool bIsPostgreSQL = STARTS_WITH(pszFilename, "postgresql://");
334
335
29
    if (!pszADBCDriverName)
336
29
    {
337
29
        if (m_bIsDuckDBDataset || bIsParquet)
338
0
        {
339
0
            pszADBCDriverName =
340
#ifdef _WIN32
341
                "duckdb.dll"
342
#elif defined(__MACH__) && defined(__APPLE__)
343
                "libduckdb.dylib"
344
#else
345
0
                "libduckdb.so"
346
0
#endif
347
0
                ;
348
0
        }
349
29
        else if (bIsPostgreSQL)
350
0
            pszADBCDriverName = "adbc_driver_postgresql";
351
29
        else if (bIsSQLite3)
352
0
        {
353
0
            pszADBCDriverName = "adbc_driver_sqlite";
354
0
        }
355
29
        else if (CSLFetchNameValue(poOpenInfo->papszOpenOptions,
356
29
                                   "BIGQUERY_PROJECT_ID") ||
357
29
                 CSLFetchNameValue(poOpenInfo->papszOpenOptions,
358
29
                                   "BIGQUERY_DATASET_ID") ||
359
29
                 CSLFetchNameValue(poOpenInfo->papszOpenOptions,
360
29
                                   "BIGQUERY_JSON_CREDENTIAL_STRING") ||
361
29
                 CSLFetchNameValue(poOpenInfo->papszOpenOptions,
362
29
                                   "BIGQUERY_JSON_CREDENTIAL_FILE"))
363
0
        {
364
0
            m_bIsBigQuery = true;
365
0
            pszADBCDriverName = "adbc_driver_bigquery";
366
0
        }
367
29
        else
368
29
        {
369
29
            CPLError(CE_Failure, CPLE_AppDefined,
370
29
                     "Open option ADBC_DRIVER must be specified");
371
29
            return false;
372
29
        }
373
29
    }
374
375
0
    if (poOpenInfo->eAccess == GA_Update && !m_bIsBigQuery)
376
0
    {
377
0
        return false;
378
0
    }
379
380
0
    eAccess = poOpenInfo->eAccess;
381
382
0
    m_bIsDuckDBDriver =
383
0
        m_bIsDuckDBDataset || bIsParquet ||
384
0
        (pszADBCDriverName && strstr(pszADBCDriverName, "duckdb"));
385
386
    // Load the driver
387
0
    if (m_bIsDuckDBDriver)
388
0
    {
389
0
        if (OGRADBCLoadDriver(pszADBCDriverName, "duckdb_adbc_init", &m_driver,
390
0
                              error) != ADBC_STATUS_OK)
391
0
        {
392
0
            CPLError(CE_Failure, CPLE_AppDefined, "AdbcLoadDriver() failed: %s",
393
0
                     error.message());
394
0
            return false;
395
0
        }
396
0
    }
397
0
    else
398
0
    {
399
0
        if (OGRADBCLoadDriver(pszADBCDriverName, nullptr, &m_driver, error) !=
400
0
            ADBC_STATUS_OK)
401
0
        {
402
0
            CPLError(CE_Failure, CPLE_AppDefined, "AdbcLoadDriver() failed: %s",
403
0
                     error.message());
404
0
            return false;
405
0
        }
406
0
    }
407
408
    // Allocate the database
409
0
    if (ADBC_CALL(DatabaseNew, &m_database, error) != ADBC_STATUS_OK)
410
0
    {
411
0
        CPLError(CE_Failure, CPLE_AppDefined, "AdbcDatabaseNew() failed: %s",
412
0
                 error.message());
413
0
        return false;
414
0
    }
415
416
    // Set options
417
0
    if (m_bIsDuckDBDriver && pszFilename[0])
418
0
    {
419
0
        VSIStatBuf sStatBuf;
420
0
        if (!bIsParquet && VSIStat(pszFilename, &sStatBuf) != 0 &&
421
0
            strcmp(pszFilename, ":memory:") != 0)
422
0
        {
423
0
            CPLError(CE_Failure, CPLE_AppDefined, "%s does not exist",
424
0
                     pszFilename);
425
0
            return false;
426
0
        }
427
0
        if (ADBC_CALL(DatabaseSetOption, &m_database, "path",
428
0
                      bIsParquet ? ":memory:" : pszFilename,
429
0
                      error) != ADBC_STATUS_OK)
430
0
        {
431
0
            CPLError(CE_Failure, CPLE_AppDefined,
432
0
                     "AdbcDatabaseSetOption() failed: %s", error.message());
433
0
            return false;
434
0
        }
435
0
    }
436
0
    else if (pszFilename[0])
437
0
    {
438
0
        if (ADBC_CALL(DatabaseSetOption, &m_database, "uri", pszFilename,
439
0
                      error) != ADBC_STATUS_OK)
440
0
        {
441
0
            CPLError(CE_Failure, CPLE_AppDefined,
442
0
                     "AdbcDatabaseSetOption() failed: %s", error.message());
443
0
            return false;
444
0
        }
445
0
    }
446
447
0
    const auto GetAsOpenOptionOrConfigOption = [poOpenInfo](const char *pszKey)
448
0
    {
449
0
        const char *pszVal =
450
0
            CSLFetchNameValue(poOpenInfo->papszOpenOptions, pszKey);
451
0
        if (pszVal)
452
0
            return pszVal;
453
        // Below comments are for scripts/collect_config_options.py
454
        // CPLGetConfigOption("BIGQUERY_PROJECT_ID", nullptr);
455
        // CPLGetConfigOption("BIGQUERY_DATASET_ID", nullptr);
456
        // CPLGetConfigOption("BIGQUERY_JSON_CREDENTIAL_STRING", nullptr);
457
        // CPLGetConfigOption("BIGQUERY_JSON_CREDENTIAL_FILE", nullptr);
458
0
        return CPLGetConfigOption(pszKey, nullptr);
459
0
    };
460
461
0
    const char *pszBIGQUERY_PROJECT_ID =
462
0
        GetAsOpenOptionOrConfigOption("BIGQUERY_PROJECT_ID");
463
0
    if (pszBIGQUERY_PROJECT_ID && pszBIGQUERY_PROJECT_ID[0])
464
0
    {
465
0
        if (ADBC_CALL(DatabaseSetOption, &m_database,
466
0
                      "adbc.bigquery.sql.project_id", pszBIGQUERY_PROJECT_ID,
467
0
                      error) != ADBC_STATUS_OK)
468
0
        {
469
0
            CPLError(CE_Failure, CPLE_AppDefined,
470
0
                     "AdbcDatabaseSetOption() failed: %s", error.message());
471
0
            return false;
472
0
        }
473
0
    }
474
475
0
    const char *pszBIGQUERY_DATASET_ID =
476
0
        GetAsOpenOptionOrConfigOption("BIGQUERY_DATASET_ID");
477
0
    if (pszBIGQUERY_DATASET_ID && pszBIGQUERY_DATASET_ID[0])
478
0
    {
479
0
        m_osBigQueryDatasetId = pszBIGQUERY_DATASET_ID;
480
0
        if (ADBC_CALL(DatabaseSetOption, &m_database,
481
0
                      "adbc.bigquery.sql.dataset_id", pszBIGQUERY_DATASET_ID,
482
0
                      error) != ADBC_STATUS_OK)
483
0
        {
484
0
            CPLError(CE_Failure, CPLE_AppDefined,
485
0
                     "AdbcDatabaseSetOption() failed: %s", error.message());
486
0
            return false;
487
0
        }
488
0
    }
489
490
0
    const char *pszBIGQUERY_JSON_CREDENTIAL_STRING =
491
0
        GetAsOpenOptionOrConfigOption("BIGQUERY_JSON_CREDENTIAL_STRING");
492
0
    const char *pszBIGQUERY_JSON_CREDENTIAL_FILE =
493
0
        GetAsOpenOptionOrConfigOption("BIGQUERY_JSON_CREDENTIAL_FILE");
494
0
    if (pszBIGQUERY_JSON_CREDENTIAL_STRING &&
495
0
        pszBIGQUERY_JSON_CREDENTIAL_STRING[0])
496
0
    {
497
0
        if (pszBIGQUERY_JSON_CREDENTIAL_FILE &&
498
0
            pszBIGQUERY_JSON_CREDENTIAL_FILE[0])
499
0
        {
500
0
            CPLError(CE_Warning, CPLE_AppDefined,
501
0
                     "BIGQUERY_JSON_CREDENTIAL_FILE ignored when "
502
0
                     "BIGQUERY_JSON_CREDENTIAL_STRING is set");
503
0
        }
504
505
0
        if (ADBC_CALL(DatabaseSetOption, &m_database,
506
0
                      "adbc.bigquery.sql.auth_credentials",
507
0
                      "adbc.bigquery.sql.auth_type.json_credential_string",
508
0
                      error) != ADBC_STATUS_OK)
509
0
        {
510
0
            CPLError(CE_Failure, CPLE_AppDefined,
511
0
                     "AdbcDatabaseSetOption() failed: %s", error.message());
512
0
            return false;
513
0
        }
514
515
0
        if (ADBC_CALL(DatabaseSetOption, &m_database,
516
0
                      "adbc.bigquery.sql.auth_credentials",
517
0
                      pszBIGQUERY_JSON_CREDENTIAL_STRING,
518
0
                      error) != ADBC_STATUS_OK)
519
0
        {
520
0
            CPLError(CE_Failure, CPLE_AppDefined,
521
0
                     "AdbcDatabaseSetOption() failed: %s", error.message());
522
0
            return false;
523
0
        }
524
0
    }
525
0
    else if (pszBIGQUERY_JSON_CREDENTIAL_FILE &&
526
0
             pszBIGQUERY_JSON_CREDENTIAL_FILE[0])
527
0
    {
528
0
        if (ADBC_CALL(DatabaseSetOption, &m_database,
529
0
                      "adbc.bigquery.sql.auth_credentials",
530
0
                      "adbc.bigquery.sql.auth_type.json_credential_file",
531
0
                      error) != ADBC_STATUS_OK)
532
0
        {
533
0
            CPLError(CE_Failure, CPLE_AppDefined,
534
0
                     "AdbcDatabaseSetOption() failed: %s", error.message());
535
0
            return false;
536
0
        }
537
538
0
        if (ADBC_CALL(DatabaseSetOption, &m_database,
539
0
                      "adbc.bigquery.sql.auth_credentials",
540
0
                      pszBIGQUERY_JSON_CREDENTIAL_FILE,
541
0
                      error) != ADBC_STATUS_OK)
542
0
        {
543
0
            CPLError(CE_Failure, CPLE_AppDefined,
544
0
                     "AdbcDatabaseSetOption() failed: %s", error.message());
545
0
            return false;
546
0
        }
547
0
    }
548
549
0
    if (m_bIsBigQuery)
550
0
    {
551
0
        if (!(pszBIGQUERY_PROJECT_ID && pszBIGQUERY_PROJECT_ID[0]))
552
0
        {
553
0
            CPLError(CE_Failure, CPLE_AppDefined,
554
0
                     "Required BIGQUERY_PROJECT_ID open option not provided");
555
0
            return false;
556
0
        }
557
0
        if (!(pszBIGQUERY_JSON_CREDENTIAL_STRING &&
558
0
              pszBIGQUERY_JSON_CREDENTIAL_STRING[0]) &&
559
0
            !(pszBIGQUERY_JSON_CREDENTIAL_FILE &&
560
0
              pszBIGQUERY_JSON_CREDENTIAL_FILE[0]))
561
0
        {
562
0
            CPLError(CE_Failure, CPLE_AppDefined,
563
0
                     "Required BIGQUERY_JSON_CREDENTIAL_STRING or "
564
0
                     "BIGQUERY_JSON_CREDENTIAL_FILE open option not provided");
565
0
            return false;
566
0
        }
567
0
    }
568
569
0
    for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(
570
0
             static_cast<CSLConstList>(poOpenInfo->papszOpenOptions)))
571
0
    {
572
0
        if (STARTS_WITH_CI(pszKey, "ADBC_OPTION_"))
573
0
        {
574
0
            if (ADBC_CALL(DatabaseSetOption, &m_database,
575
0
                          pszKey + strlen("ADBC_OPTION_"), pszValue,
576
0
                          error) != ADBC_STATUS_OK)
577
0
            {
578
0
                CPLError(CE_Failure, CPLE_AppDefined,
579
0
                         "AdbcDatabaseSetOption() failed: %s", error.message());
580
0
                return false;
581
0
            }
582
0
        }
583
0
    }
584
585
0
    if (ADBC_CALL(DatabaseInit, &m_database, error) != ADBC_STATUS_OK)
586
0
    {
587
0
        CPLError(CE_Failure, CPLE_AppDefined, "AdbcDatabaseInit() failed: %s",
588
0
                 error.message());
589
0
        return false;
590
0
    }
591
592
0
    m_connection = std::make_unique<AdbcConnection>();
593
0
    if (ADBC_CALL(ConnectionNew, m_connection.get(), error) != ADBC_STATUS_OK)
594
0
    {
595
0
        CPLError(CE_Failure, CPLE_AppDefined, "AdbcConnectionNew() failed: %s",
596
0
                 error.message());
597
0
        return false;
598
0
    }
599
600
0
    if (ADBC_CALL(ConnectionInit, m_connection.get(), &m_database, error) !=
601
0
        ADBC_STATUS_OK)
602
0
    {
603
0
        CPLError(CE_Failure, CPLE_AppDefined, "AdbcConnectionInit() failed: %s",
604
0
                 error.message());
605
0
        return false;
606
0
    }
607
608
0
    char **papszPreludeStatements = CSLFetchNameValueMultiple(
609
0
        poOpenInfo->papszOpenOptions, "PRELUDE_STATEMENTS");
610
0
    for (const char *pszStatement :
611
0
         cpl::Iterate(CSLConstList(papszPreludeStatements)))
612
0
    {
613
0
        CPL_IGNORE_RET_VAL(CreateInternalLayer(pszStatement)->GotError());
614
0
    }
615
0
    CSLDestroy(papszPreludeStatements);
616
0
    if (m_bIsDuckDBDriver && CPLTestBool(CPLGetConfigOption(
617
0
                                 "OGR_ADBC_AUTO_LOAD_DUCKDB_SPATIAL", "ON")))
618
0
    {
619
0
        auto poTmpLayer =
620
0
            CreateInternalLayer("SELECT 1 FROM duckdb_extensions() WHERE "
621
0
                                "extension_name='spatial' AND loaded = false");
622
0
        if (!poTmpLayer->GotError() &&
623
0
            std::unique_ptr<OGRFeature>(poTmpLayer->GetNextFeature()) !=
624
0
                nullptr)
625
0
        {
626
0
            CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
627
0
            CPL_IGNORE_RET_VAL(CreateInternalLayer("LOAD spatial")->GotError());
628
0
        }
629
630
0
        poTmpLayer =
631
0
            CreateInternalLayer("SELECT 1 FROM duckdb_extensions() WHERE "
632
0
                                "extension_name='spatial' AND loaded = true");
633
0
        m_bSpatialLoaded = !poTmpLayer->GotError() &&
634
0
                           std::unique_ptr<OGRFeature>(
635
0
                               poTmpLayer->GetNextFeature()) != nullptr;
636
0
    }
637
638
0
    std::string osLayerName = "RESULTSET";
639
0
    std::string osSQL;
640
0
    bool bIsParquetLayer = false;
641
0
    if (bIsParquet)
642
0
    {
643
0
        if (m_osParquetFilename.empty())
644
0
            m_osParquetFilename = pszFilename;
645
0
        osLayerName = CPLGetBasenameSafe(m_osParquetFilename.c_str());
646
0
        if (osLayerName == "*")
647
0
            osLayerName = CPLGetBasenameSafe(
648
0
                CPLGetDirnameSafe(m_osParquetFilename.c_str()).c_str());
649
0
        if (!pszSQL)
650
0
        {
651
0
            osSQL =
652
0
                CPLSPrintf("SELECT * FROM read_parquet('%s')",
653
0
                           OGRDuplicateCharacter(pszFilename, '\'').c_str());
654
0
            pszSQL = osSQL.c_str();
655
0
            bIsParquetLayer = true;
656
0
        }
657
0
    }
658
659
0
    if (pszSQL)
660
0
    {
661
0
        if (pszSQL[0])
662
0
        {
663
0
            std::unique_ptr<OGRADBCLayer> poLayer;
664
0
            if ((bIsParquet || m_bIsDuckDBDataset) && m_bSpatialLoaded)
665
0
            {
666
0
                std::string osErrorMsg;
667
0
                {
668
0
                    CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
669
0
                    poLayer = CreateLayer(pszSQL, osLayerName.c_str(), false);
670
0
                    if (poLayer->GotError())
671
0
                        osErrorMsg = CPLGetLastErrorMsg();
672
0
                }
673
0
                if (poLayer->GotError())
674
0
                {
675
0
                    CPLDebug("ADBC",
676
0
                             "Connecting with 'LOAD spatial' did not work "
677
0
                             "(%s). Retrying without it",
678
0
                             osErrorMsg.c_str());
679
0
                    ADBC_CALL(ConnectionRelease, m_connection.get(), error);
680
0
                    m_connection.reset();
681
682
0
                    ADBC_CALL(DatabaseRelease, &m_database, error);
683
0
                    memset(&m_database, 0, sizeof(m_database));
684
685
0
                    if (ADBC_CALL(DatabaseNew, &m_database, error) !=
686
0
                        ADBC_STATUS_OK)
687
0
                    {
688
0
                        CPLError(CE_Failure, CPLE_AppDefined,
689
0
                                 "AdbcDatabaseNew() failed: %s",
690
0
                                 error.message());
691
0
                        return false;
692
0
                    }
693
0
                    if (ADBC_CALL(DatabaseSetOption, &m_database, "path",
694
0
                                  ":memory:", error) != ADBC_STATUS_OK)
695
0
                    {
696
0
                        CPLError(CE_Failure, CPLE_AppDefined,
697
0
                                 "AdbcDatabaseSetOption() failed: %s",
698
0
                                 error.message());
699
0
                        return false;
700
0
                    }
701
702
0
                    if (ADBC_CALL(DatabaseInit, &m_database, error) !=
703
0
                        ADBC_STATUS_OK)
704
0
                    {
705
0
                        CPLError(CE_Failure, CPLE_AppDefined,
706
0
                                 "AdbcDatabaseInit() failed: %s",
707
0
                                 error.message());
708
0
                        return false;
709
0
                    }
710
711
0
                    m_connection = std::make_unique<AdbcConnection>();
712
0
                    if (ADBC_CALL(ConnectionNew, m_connection.get(), error) !=
713
0
                        ADBC_STATUS_OK)
714
0
                    {
715
0
                        CPLError(CE_Failure, CPLE_AppDefined,
716
0
                                 "AdbcConnectionNew() failed: %s",
717
0
                                 error.message());
718
0
                        return false;
719
0
                    }
720
721
0
                    if (ADBC_CALL(ConnectionInit, m_connection.get(),
722
0
                                  &m_database, error) != ADBC_STATUS_OK)
723
0
                    {
724
0
                        CPLError(CE_Failure, CPLE_AppDefined,
725
0
                                 "AdbcConnectionInit() failed: %s",
726
0
                                 error.message());
727
0
                        return false;
728
0
                    }
729
0
                }
730
0
            }
731
0
            if (!poLayer || poLayer->GotError())
732
0
            {
733
0
                if (m_bIsBigQuery)
734
0
                    poLayer = std::make_unique<OGRADBCBigQueryLayer>(
735
0
                        this, osLayerName.c_str(), pszSQL,
736
0
                        /* bInternalUse = */ false);
737
0
                else
738
0
                    poLayer = CreateLayer(pszSQL, osLayerName.c_str(), false);
739
0
                if (poLayer->GotError())
740
0
                    return false;
741
0
            }
742
743
0
            poLayer->m_bIsParquetLayer = bIsParquetLayer;
744
0
            m_apoLayers.emplace_back(std::move(poLayer));
745
0
        }
746
0
    }
747
0
    else if (m_bIsDuckDBDataset || bIsSQLite3)
748
0
    {
749
0
        auto poLayerList = CreateInternalLayer(
750
0
            "SELECT name FROM sqlite_master WHERE type IN ('table', 'view')");
751
0
        if (poLayerList->GotError() ||
752
0
            poLayerList->GetLayerDefn()->GetFieldCount() != 1)
753
0
        {
754
0
            return false;
755
0
        }
756
757
0
        for (const auto &poFeature : poLayerList.get())
758
0
        {
759
0
            const char *pszLayerName = poFeature->GetFieldAsString(0);
760
0
            if (bIsSQLite3 && EQUAL(pszLayerName, "SpatialIndex"))
761
0
                continue;
762
0
            const std::string osStatement =
763
0
                CPLSPrintf("SELECT * FROM \"%s\"",
764
0
                           OGRDuplicateCharacter(pszLayerName, '"').c_str());
765
0
            m_apoLayers.emplace_back(
766
0
                CreateLayer(osStatement.c_str(), pszLayerName, false));
767
0
        }
768
0
    }
769
0
    else if (bIsPostgreSQL)
770
0
    {
771
0
        auto poLayerList = CreateInternalLayer(
772
0
            "SELECT n.nspname, c.relname FROM pg_class c "
773
0
            "JOIN pg_namespace n ON c.relnamespace = n.oid "
774
0
            "AND c.relkind in ('r','v','m','f') "
775
0
            "AND n.nspname NOT IN ('pg_catalog', 'information_schema') "
776
0
            "ORDER BY c.oid");
777
0
        if (poLayerList->GotError() ||
778
0
            poLayerList->GetLayerDefn()->GetFieldCount() != 2)
779
0
        {
780
0
            return false;
781
0
        }
782
783
0
        for (const auto &poFeature : poLayerList.get())
784
0
        {
785
0
            const char *pszNamespace = poFeature->GetFieldAsString(0);
786
0
            const char *pszTableName = poFeature->GetFieldAsString(1);
787
0
            const std::string osStatement =
788
0
                CPLSPrintf("SELECT * FROM \"%s\".\"%s\"",
789
0
                           OGRDuplicateCharacter(pszNamespace, '"').c_str(),
790
0
                           OGRDuplicateCharacter(pszTableName, '"').c_str());
791
792
0
            m_apoLayers.emplace_back(CreateLayer(
793
0
                osStatement.c_str(),
794
0
                CPLSPrintf("%s.%s", pszNamespace, pszTableName), false));
795
0
        }
796
0
    }
797
0
    else if (m_bIsBigQuery)
798
0
    {
799
0
        if (!(pszBIGQUERY_DATASET_ID && pszBIGQUERY_DATASET_ID[0]))
800
0
        {
801
0
            CPLError(CE_Failure, CPLE_AppDefined,
802
0
                     "Cannot list tables when BIGQUERY_DATASET_ID open option "
803
0
                     "is not provided");
804
0
            return false;
805
0
        }
806
0
        const std::string s(pszBIGQUERY_DATASET_ID);
807
0
        if (!std::all_of(s.begin(), s.end(),
808
0
                         [](char c) { return std::isalnum(c) || c == '_'; }))
809
0
        {
810
0
            CPLError(CE_Failure, CPLE_AppDefined,
811
0
                     "Invalid characters found in BIGQUERY_DATASET_ID value");
812
0
            return false;
813
0
        }
814
0
        auto poLayerList = CreateInternalLayer(
815
0
            CPLSPrintf("SELECT table_name FROM %s.INFORMATION_SCHEMA.TABLES "
816
0
                       "ORDER BY creation_time",
817
0
                       pszBIGQUERY_DATASET_ID));
818
0
        if (poLayerList->GotError() ||
819
0
            poLayerList->GetLayerDefn()->GetFieldCount() != 1)
820
0
        {
821
0
            return false;
822
0
        }
823
824
0
        for (const auto &poFeature : poLayerList.get())
825
0
        {
826
0
            const char *pszTableName = poFeature->GetFieldAsString(0);
827
0
            const std::string osStatement = CPLSPrintf(
828
0
                "SELECT * FROM `%s`.`%s`",
829
0
                OGRDuplicateCharacter(pszBIGQUERY_DATASET_ID, '`').c_str(),
830
0
                OGRDuplicateCharacter(pszTableName, '`').c_str());
831
832
0
            m_apoLayers.emplace_back(std::make_unique<OGRADBCBigQueryLayer>(
833
0
                this, pszTableName, osStatement,
834
0
                /* bInternalUse = */ false));
835
0
        }
836
0
    }
837
838
0
    return true;
839
0
}
840
841
/************************************************************************/
842
/*                          ICreateLayer()                              */
843
/************************************************************************/
844
845
OGRLayer *OGRADBCDataset::ICreateLayer(const char *pszName,
846
                                       const OGRGeomFieldDefn *poGeomFieldDefn,
847
                                       CSLConstList papszOptions)
848
0
{
849
0
    if (!m_bIsBigQuery)
850
0
    {
851
0
        CPLError(CE_Failure, CPLE_NotSupported,
852
0
                 "CreateLayer() only supported for BigQuery");
853
0
        return nullptr;
854
0
    }
855
0
    if (GetAccess() != GA_Update)
856
0
    {
857
0
        CPLError(
858
0
            CE_Failure, CPLE_NotSupported,
859
0
            "CreateLayer() only supported on datasets opened in update mode");
860
0
        return nullptr;
861
0
    }
862
0
    if (m_osBigQueryDatasetId.empty())
863
0
    {
864
0
        CPLError(CE_Failure, CPLE_AppDefined,
865
0
                 "Open option BIGQUERY_DATASET_ID should be set");
866
0
        return nullptr;
867
0
    }
868
869
0
    if (GetLayerByName(pszName))
870
0
    {
871
0
        CPLError(CE_Failure, CPLE_AppDefined, "Table %s already exists",
872
0
                 pszName);
873
0
        return nullptr;
874
0
    }
875
876
0
    if (poGeomFieldDefn)
877
0
    {
878
0
        const auto poSRS = poGeomFieldDefn->GetSpatialRef();
879
0
        if (poSRS && !poSRS->IsGeographic())
880
0
        {
881
0
            CPLError(CE_Failure, CPLE_NotSupported,
882
0
                     "BigQuery only supports geographic CRS. Please reproject "
883
0
                     "your layer to one (typically EPSG:4326)");
884
0
            return nullptr;
885
0
        }
886
0
    }
887
888
0
    const std::string osStatement = CPLSPrintf(
889
0
        "SELECT * FROM `%s`.`%s`",
890
0
        OGRDuplicateCharacter(m_osBigQueryDatasetId.c_str(), '`').c_str(),
891
0
        OGRDuplicateCharacter(pszName, '`').c_str());
892
893
0
    const char *pszFIDColName =
894
0
        CSLFetchNameValueDef(papszOptions, "FID", "ogc_fid");
895
0
    auto poLayer =
896
0
        std::make_unique<OGRADBCBigQueryLayer>(this, pszName, osStatement,
897
0
                                               /* bInternalUse = */ false);
898
0
    poLayer->SetDeferredCreation(pszFIDColName, poGeomFieldDefn);
899
0
    m_apoLayers.emplace_back(std::move(poLayer));
900
0
    return m_apoLayers.back().get();
901
0
}
902
903
/************************************************************************/
904
/*                           DeleteLayer()                              */
905
/************************************************************************/
906
907
OGRErr OGRADBCDataset::DeleteLayer(int iLayer)
908
0
{
909
0
    if (!m_bIsBigQuery)
910
0
    {
911
0
        CPLError(CE_Failure, CPLE_NotSupported,
912
0
                 "DeleteLayer() only supported for BigQuery");
913
0
        return OGRERR_FAILURE;
914
0
    }
915
0
    if (GetAccess() != GA_Update)
916
0
    {
917
0
        CPLError(
918
0
            CE_Failure, CPLE_NotSupported,
919
0
            "DeleteLayer() only supported on datasets opened in update mode");
920
0
        return OGRERR_FAILURE;
921
0
    }
922
0
    if (iLayer < 0 || static_cast<size_t>(iLayer) >= m_apoLayers.size())
923
0
    {
924
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid layer index");
925
0
        return OGRERR_FAILURE;
926
0
    }
927
928
0
    auto poADBCLayer =
929
0
        dynamic_cast<OGRADBCBigQueryLayer *>(m_apoLayers[iLayer].get());
930
0
    if (poADBCLayer && !poADBCLayer->m_bDeferredCreation)
931
0
    {
932
0
        std::string osDatasetId;
933
0
        std::string osTableId;
934
0
        if (!poADBCLayer->GetBigQueryDatasetAndTableId(osDatasetId, osTableId))
935
0
        {
936
0
            CPLError(CE_Failure, CPLE_NotSupported,
937
0
                     "DeleteLayer(): cannot get dataset and table ID");
938
0
            return OGRERR_FAILURE;
939
0
        }
940
941
0
        std::string osSQL = "DROP TABLE `";
942
0
        osSQL += OGRDuplicateCharacter(osDatasetId.c_str(), '`');
943
0
        osSQL += "`.`";
944
0
        osSQL += OGRDuplicateCharacter(osTableId.c_str(), '`');
945
0
        osSQL += "`";
946
        // CPLDebug("ADBC", "%s", osSQL.c_str());
947
0
        if (CreateInternalLayer(osSQL.c_str())->GotError())
948
0
        {
949
0
            return OGRERR_FAILURE;
950
0
        }
951
0
    }
952
953
0
    m_apoLayers.erase(m_apoLayers.begin() + iLayer);
954
0
    return OGRERR_NONE;
955
0
}
956
957
/************************************************************************/
958
/*                         TestCapability()                             */
959
/************************************************************************/
960
961
int OGRADBCDataset::TestCapability(const char *pszCap) const
962
0
{
963
0
    if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer))
964
0
        return m_bIsBigQuery && eAccess == GA_Update;
965
0
    return false;
966
0
}
967
968
/************************************************************************/
969
/*                         GetLayerByName()                             */
970
/************************************************************************/
971
972
OGRLayer *OGRADBCDataset::GetLayerByName(const char *pszName)
973
0
{
974
0
    OGRLayer *poLayer = GDALDataset::GetLayerByName(pszName);
975
0
    if (poLayer || !EQUAL(pszName, "table_list"))
976
0
        return poLayer;
977
978
0
    OGRADBCError error;
979
0
    auto objectsStream = std::make_unique<OGRArrowArrayStream>();
980
0
    ADBC_CALL(ConnectionGetObjects, m_connection.get(),
981
0
              ADBC_OBJECT_DEPTH_TABLES,
982
0
              /* catalog = */ nullptr,
983
0
              /* db_schema = */ nullptr,
984
0
              /* table_name = */ nullptr,
985
0
              /* table_type = */ nullptr,
986
0
              /* column_name = */ nullptr, objectsStream->get(), error);
987
988
0
    ArrowSchema schema = {};
989
0
    if (objectsStream->get_schema(&schema) != 0)
990
0
    {
991
0
        CPLError(CE_Failure, CPLE_AppDefined, "get_schema() failed");
992
0
        return nullptr;
993
0
    }
994
995
0
    OGRADBCLayer tmpLayer(this, "", std::move(objectsStream), &schema,
996
0
                          /* bInternalUse = */ true);
997
0
    const auto tmpLayerDefn = tmpLayer.GetLayerDefn();
998
0
    if (tmpLayerDefn->GetFieldIndex("catalog_name") < 0 ||
999
0
        tmpLayerDefn->GetFieldIndex("catalog_db_schemas") < 0)
1000
0
    {
1001
0
        return nullptr;
1002
0
    }
1003
1004
0
    auto poTableListLayer =
1005
0
        std::make_unique<OGRMemLayer>("table_list", nullptr, wkbNone);
1006
0
    {
1007
0
        OGRFieldDefn oField("catalog_name", OFTString);
1008
0
        poTableListLayer->CreateField(&oField);
1009
0
    }
1010
0
    {
1011
0
        OGRFieldDefn oField("schema_name", OFTString);
1012
0
        poTableListLayer->CreateField(&oField);
1013
0
    }
1014
0
    {
1015
0
        OGRFieldDefn oField("table_name", OFTString);
1016
0
        poTableListLayer->CreateField(&oField);
1017
0
    }
1018
0
    {
1019
0
        OGRFieldDefn oField("table_type", OFTString);
1020
0
        poTableListLayer->CreateField(&oField);
1021
0
    }
1022
1023
0
    for (const auto &poFeature : tmpLayer)
1024
0
    {
1025
0
        const char *pszCatalogName =
1026
0
            poFeature->GetFieldAsString("catalog_name");
1027
0
        const char *pszCatalogDBSchemas =
1028
0
            poFeature->GetFieldAsString("catalog_db_schemas");
1029
0
        if (pszCatalogDBSchemas)
1030
0
        {
1031
0
            CPLJSONDocument oDoc;
1032
0
            if (oDoc.LoadMemory(pszCatalogDBSchemas))
1033
0
            {
1034
0
                auto oRoot = oDoc.GetRoot();
1035
0
                if (oRoot.GetType() == CPLJSONObject::Type::Array)
1036
0
                {
1037
0
                    for (const auto &oSchema : oRoot.ToArray())
1038
0
                    {
1039
0
                        if (oSchema.GetType() == CPLJSONObject::Type::Object)
1040
0
                        {
1041
0
                            const std::string osSchemaName =
1042
0
                                oSchema.GetString("schema_name");
1043
0
                            const auto oTables =
1044
0
                                oSchema.GetArray("db_schema_tables");
1045
0
                            if (oTables.IsValid())
1046
0
                            {
1047
0
                                for (const auto &oTable : oTables)
1048
0
                                {
1049
0
                                    if (oTable.GetType() ==
1050
0
                                        CPLJSONObject::Type::Object)
1051
0
                                    {
1052
0
                                        const std::string osTableName =
1053
0
                                            oTable.GetString("table_name");
1054
0
                                        const std::string osTableType =
1055
0
                                            oTable.GetString("table_type");
1056
0
                                        if (!osTableName.empty() &&
1057
0
                                            osTableType != "index" &&
1058
0
                                            osTableType != "trigger")
1059
0
                                        {
1060
0
                                            auto poFeat =
1061
0
                                                std::make_unique<OGRFeature>(
1062
0
                                                    poTableListLayer
1063
0
                                                        ->GetLayerDefn());
1064
0
                                            if (pszCatalogName)
1065
0
                                                poFeat->SetField(
1066
0
                                                    "catalog_name",
1067
0
                                                    pszCatalogName);
1068
0
                                            if (oSchema.GetObj("schema_name")
1069
0
                                                    .IsValid())
1070
0
                                                poFeat->SetField(
1071
0
                                                    "schema_name",
1072
0
                                                    osSchemaName.c_str());
1073
0
                                            poFeat->SetField(
1074
0
                                                "table_name",
1075
0
                                                osTableName.c_str());
1076
0
                                            if (oTable.GetObj("table_type")
1077
0
                                                    .IsValid())
1078
0
                                                poFeat->SetField(
1079
0
                                                    "table_type",
1080
0
                                                    osTableType.c_str());
1081
0
                                            CPL_IGNORE_RET_VAL(
1082
0
                                                poTableListLayer->CreateFeature(
1083
0
                                                    std::move(poFeat)));
1084
0
                                        }
1085
0
                                    }
1086
0
                                }
1087
0
                            }
1088
0
                        }
1089
0
                    }
1090
0
                }
1091
0
            }
1092
0
        }
1093
0
    }
1094
1095
0
    m_apoLayers.emplace_back(std::move(poTableListLayer));
1096
0
    return m_apoLayers.back().get();
1097
0
}
1098
1099
#undef ADBC_CALL