Coverage Report

Created: 2026-05-16 08:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/adbc/ogradbclayer.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 "ogr_spatialref.h"
16
#include "ogr_p.h"
17
#include "cpl_json.h"
18
19
#include <cassert>
20
#include <cmath>
21
#include <limits>
22
#include <map>
23
#include <set>
24
25
0
#define ADBC_CALL(func, ...) m_poDS->m_driver.func(__VA_ARGS__)
26
27
OGRArrowArrayToOGRFeatureAdapterLayer::~OGRArrowArrayToOGRFeatureAdapterLayer()
28
0
{
29
0
    m_poLayerDefn->Release();
30
0
}
31
32
/************************************************************************/
33
/*                     GetGeometryTypeFromString()                      */
34
/************************************************************************/
35
36
static OGRwkbGeometryType GetGeometryTypeFromString(const std::string &osType)
37
0
{
38
0
    OGRwkbGeometryType eGeomType = wkbUnknown;
39
0
    OGRReadWKTGeometryType(osType.c_str(), &eGeomType);
40
0
    if (eGeomType == wkbUnknown && !osType.empty())
41
0
    {
42
0
        CPLDebug("ADBC", "Unknown geometry type: %s", osType.c_str());
43
0
    }
44
0
    return eGeomType;
45
0
}
46
47
/************************************************************************/
48
/*                            OGRADBCLayer()                            */
49
/************************************************************************/
50
51
OGRADBCLayer::OGRADBCLayer(OGRADBCDataset *poDS, const char *pszName,
52
                           const std::string &osStatement, bool bInternalUse)
53
0
    : m_poDS(poDS), m_osBaseStatement(osStatement),
54
0
      m_osModifiedBaseStatement(m_osBaseStatement), m_bInternalUse(bInternalUse)
55
0
{
56
0
    SetDescription(pszName);
57
58
0
    memset(&m_schema, 0, sizeof(m_schema));
59
0
}
60
61
/************************************************************************/
62
/*                            OGRADBCLayer()                            */
63
/************************************************************************/
64
65
OGRADBCLayer::OGRADBCLayer(OGRADBCDataset *poDS, const char *pszName,
66
                           std::unique_ptr<OGRArrowArrayStream> poStream,
67
                           ArrowSchema *schema, bool bInternalUse)
68
0
    : m_poDS(poDS), m_stream(std::move(poStream)), m_bInternalUse(bInternalUse)
69
0
{
70
0
    SetDescription(pszName);
71
72
0
    memcpy(&m_schema, schema, sizeof(m_schema));
73
0
    schema->release = nullptr;
74
0
}
75
76
/************************************************************************/
77
/*                            GetLayerDefn()                            */
78
/************************************************************************/
79
80
const OGRFeatureDefn *OGRADBCLayer::GetLayerDefn() const
81
0
{
82
0
    if (!m_poAdapterLayer)
83
0
        const_cast<OGRADBCLayer *>(this)->BuildLayerDefn();
84
0
    assert(m_poAdapterLayer);
85
0
    return m_poAdapterLayer->GetLayerDefn();
86
0
}
87
88
/************************************************************************/
89
/*                            GetFIDColumn()                            */
90
/************************************************************************/
91
92
const char *OGRADBCLayer::GetFIDColumn() const
93
0
{
94
0
    if (!m_poAdapterLayer)
95
0
        const_cast<OGRADBCLayer *>(this)->BuildLayerDefn();
96
0
    return m_osFIDColName.c_str();
97
0
}
98
99
/************************************************************************/
100
/*                              GotError()                              */
101
/************************************************************************/
102
103
bool OGRADBCLayer::GotError()
104
0
{
105
0
    if (!m_poAdapterLayer)
106
0
        BuildLayerDefn();
107
0
    return m_bLayerDefinitionError;
108
0
}
109
110
/************************************************************************/
111
/*                    ParseGeometryColumnCovering()                     */
112
/************************************************************************/
113
114
//! Parse bounding box column definition
115
static bool ParseGeometryColumnCovering(const CPLJSONObject &oJSONDef,
116
                                        std::string &osBBOXColumn,
117
                                        std::string &osXMin,
118
                                        std::string &osYMin,
119
                                        std::string &osXMax,
120
                                        std::string &osYMax)
121
0
{
122
0
    const auto oCovering = oJSONDef["covering"];
123
0
    if (oCovering.IsValid() &&
124
0
        oCovering.GetType() == CPLJSONObject::Type::Object)
125
0
    {
126
0
        const auto oBBOX = oCovering["bbox"];
127
0
        if (oBBOX.IsValid() && oBBOX.GetType() == CPLJSONObject::Type::Object)
128
0
        {
129
0
            const auto oXMin = oBBOX["xmin"];
130
0
            const auto oYMin = oBBOX["ymin"];
131
0
            const auto oXMax = oBBOX["xmax"];
132
0
            const auto oYMax = oBBOX["ymax"];
133
0
            if (oXMin.IsValid() && oYMin.IsValid() && oXMax.IsValid() &&
134
0
                oYMax.IsValid() &&
135
0
                oXMin.GetType() == CPLJSONObject::Type::Array &&
136
0
                oYMin.GetType() == CPLJSONObject::Type::Array &&
137
0
                oXMax.GetType() == CPLJSONObject::Type::Array &&
138
0
                oYMax.GetType() == CPLJSONObject::Type::Array)
139
0
            {
140
0
                const auto osXMinArray = oXMin.ToArray();
141
0
                const auto osYMinArray = oYMin.ToArray();
142
0
                const auto osXMaxArray = oXMax.ToArray();
143
0
                const auto osYMaxArray = oYMax.ToArray();
144
0
                if (osXMinArray.Size() == 2 && osYMinArray.Size() == 2 &&
145
0
                    osXMaxArray.Size() == 2 && osYMaxArray.Size() == 2 &&
146
0
                    osXMinArray[0].GetType() == CPLJSONObject::Type::String &&
147
0
                    osXMinArray[1].GetType() == CPLJSONObject::Type::String &&
148
0
                    osYMinArray[0].GetType() == CPLJSONObject::Type::String &&
149
0
                    osYMinArray[1].GetType() == CPLJSONObject::Type::String &&
150
0
                    osXMaxArray[0].GetType() == CPLJSONObject::Type::String &&
151
0
                    osXMaxArray[1].GetType() == CPLJSONObject::Type::String &&
152
0
                    osYMaxArray[0].GetType() == CPLJSONObject::Type::String &&
153
0
                    osYMaxArray[1].GetType() == CPLJSONObject::Type::String &&
154
0
                    osXMinArray[0].ToString() == osYMinArray[0].ToString() &&
155
0
                    osXMinArray[0].ToString() == osXMaxArray[0].ToString() &&
156
0
                    osXMinArray[0].ToString() == osYMaxArray[0].ToString())
157
0
                {
158
0
                    osBBOXColumn = osXMinArray[0].ToString();
159
0
                    osXMin = osXMinArray[1].ToString();
160
0
                    osYMin = osYMinArray[1].ToString();
161
0
                    osXMax = osXMaxArray[1].ToString();
162
0
                    osYMax = osYMaxArray[1].ToString();
163
0
                    return true;
164
0
                }
165
0
            }
166
0
        }
167
0
    }
168
0
    return false;
169
0
}
170
171
/************************************************************************/
172
/*                       ParseGeoParquetColumn()                        */
173
/************************************************************************/
174
175
static void ParseGeoParquetColumn(
176
    const CPLJSONObject &oColumn,
177
    std::map<std::string, OGRwkbGeometryType> &oMapType,
178
    std::map<std::string, OGREnvelope3D> &oMapExtent,
179
    std::map<std::string, OGRADBCLayer::GeomColBBOX>
180
        &oMapGeomColumnToCoveringBBOXColumn,
181
    std::map<std::string, std::unique_ptr<OGRSpatialReference>>
182
        &oMapGeomColumnsFromGeoParquet,
183
    std::set<std::string> &oSetCoveringBBoxColumn)
184
0
{
185
0
    auto oCrs = oColumn.GetObj("crs");
186
0
    if (!oCrs.IsValid())
187
0
    {
188
        // WGS 84 is implied if no crs member is found.
189
0
        auto poSRS = std::make_unique<OGRSpatialReference>();
190
0
        poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
191
0
        poSRS->importFromEPSG(4326);
192
0
        oMapGeomColumnsFromGeoParquet[oColumn.GetName()] = std::move(poSRS);
193
0
    }
194
0
    else if (oCrs.GetType() == CPLJSONObject::Type::Object)
195
0
    {
196
        // CRS encoded as PROJJSON (extension)
197
0
        const auto oType = oCrs["type"];
198
0
        if (oType.IsValid() && oType.GetType() == CPLJSONObject::Type::String)
199
0
        {
200
0
            const auto osType = oType.ToString();
201
0
            if (osType.find("CRS") != std::string::npos)
202
0
            {
203
0
                auto poSRS = std::make_unique<OGRSpatialReference>();
204
0
                poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
205
206
0
                if (poSRS->SetFromUserInput(oCrs.ToString().c_str()) ==
207
0
                    OGRERR_NONE)
208
0
                {
209
0
                    oMapGeomColumnsFromGeoParquet[oColumn.GetName()] =
210
0
                        std::move(poSRS);
211
0
                }
212
0
            }
213
0
        }
214
0
    }
215
0
    else
216
0
    {
217
0
        oMapGeomColumnsFromGeoParquet[oColumn.GetName()] = nullptr;
218
0
    }
219
220
0
    OGRwkbGeometryType eGeomType = wkbUnknown;
221
0
    auto oType = oColumn.GetObj("geometry_types");
222
0
    if (oType.GetType() == CPLJSONObject::Type::Array)
223
0
    {
224
0
        const auto oTypeArray = oType.ToArray();
225
0
        if (oTypeArray.Size() == 1)
226
0
        {
227
0
            eGeomType = GetGeometryTypeFromString(oTypeArray[0].ToString());
228
0
        }
229
0
        else if (oTypeArray.Size() > 1)
230
0
        {
231
0
            const auto PromoteToCollection = [](OGRwkbGeometryType eType)
232
0
            {
233
0
                if (eType == wkbPoint)
234
0
                    return wkbMultiPoint;
235
0
                if (eType == wkbLineString)
236
0
                    return wkbMultiLineString;
237
0
                if (eType == wkbPolygon)
238
0
                    return wkbMultiPolygon;
239
0
                return eType;
240
0
            };
241
0
            bool bMixed = false;
242
0
            bool bHasMulti = false;
243
0
            bool bHasZ = false;
244
0
            bool bHasM = false;
245
0
            const auto eFirstType = OGR_GT_Flatten(
246
0
                GetGeometryTypeFromString(oTypeArray[0].ToString()));
247
0
            const auto eFirstTypeCollection = PromoteToCollection(eFirstType);
248
0
            for (int i = 0; i < oTypeArray.Size(); ++i)
249
0
            {
250
0
                const auto eThisGeom =
251
0
                    GetGeometryTypeFromString(oTypeArray[i].ToString());
252
0
                if (PromoteToCollection(OGR_GT_Flatten(eThisGeom)) !=
253
0
                    eFirstTypeCollection)
254
0
                {
255
0
                    bMixed = true;
256
0
                    break;
257
0
                }
258
0
                bHasZ |= OGR_GT_HasZ(eThisGeom) != FALSE;
259
0
                bHasM |= OGR_GT_HasM(eThisGeom) != FALSE;
260
0
                bHasMulti |= (PromoteToCollection(OGR_GT_Flatten(eThisGeom)) ==
261
0
                              OGR_GT_Flatten(eThisGeom));
262
0
            }
263
0
            if (!bMixed)
264
0
            {
265
0
                if (eFirstTypeCollection == wkbMultiPolygon ||
266
0
                    eFirstTypeCollection == wkbMultiLineString)
267
0
                {
268
0
                    if (bHasMulti)
269
0
                        eGeomType = OGR_GT_SetModifier(eFirstTypeCollection,
270
0
                                                       bHasZ, bHasM);
271
0
                    else
272
0
                        eGeomType =
273
0
                            OGR_GT_SetModifier(eFirstType, bHasZ, bHasM);
274
0
                }
275
0
            }
276
0
        }
277
0
    }
278
279
0
    oMapType[oColumn.GetName()] = eGeomType;
280
281
0
    OGREnvelope3D sExtent;
282
0
    const auto oBBox = oColumn.GetArray("bbox");
283
0
    if (oBBox.IsValid() && oBBox.Size() == 4)
284
0
    {
285
0
        sExtent.MinX = oBBox[0].ToDouble();
286
0
        sExtent.MinY = oBBox[1].ToDouble();
287
0
        sExtent.MinZ = std::numeric_limits<double>::infinity();
288
0
        sExtent.MaxX = oBBox[2].ToDouble();
289
0
        sExtent.MaxY = oBBox[3].ToDouble();
290
0
        sExtent.MaxZ = -std::numeric_limits<double>::infinity();
291
0
        if (sExtent.MinX <= sExtent.MaxX)
292
0
        {
293
0
            oMapExtent[oColumn.GetName()] = sExtent;
294
0
        }
295
0
    }
296
0
    else if (oBBox.IsValid() && oBBox.Size() == 6)
297
0
    {
298
0
        sExtent.MinX = oBBox[0].ToDouble();
299
0
        sExtent.MinY = oBBox[1].ToDouble();
300
0
        sExtent.MinZ = oBBox[2].ToDouble();
301
0
        sExtent.MaxX = oBBox[3].ToDouble();
302
0
        sExtent.MaxY = oBBox[4].ToDouble();
303
0
        sExtent.MaxZ = oBBox[5].ToDouble();
304
0
        if (sExtent.MinX <= sExtent.MaxX)
305
0
        {
306
0
            oMapExtent[oColumn.GetName()] = sExtent;
307
0
        }
308
0
    }
309
310
0
    std::string osBBOXColumn;
311
0
    std::string osXMin, osYMin, osXMax, osYMax;
312
0
    if (ParseGeometryColumnCovering(oColumn, osBBOXColumn, osXMin, osYMin,
313
0
                                    osXMax, osYMax))
314
0
    {
315
0
        OGRADBCLayer::GeomColBBOX geomColBBOX;
316
0
        const std::string osPrefix =
317
0
            std::string("\"")
318
0
                .append(OGRDuplicateCharacter(osBBOXColumn, '"'))
319
0
                .append("\".\"");
320
321
0
        const auto BuildColName = [&osPrefix](const std::string &s)
322
0
        {
323
0
            return std::string(osPrefix)
324
0
                .append(OGRDuplicateCharacter(s, '"'))
325
0
                .append("\"");
326
0
        };
327
328
0
        geomColBBOX.osXMin = BuildColName(osXMin);
329
0
        geomColBBOX.osYMin = BuildColName(osYMin);
330
0
        geomColBBOX.osXMax = BuildColName(osXMax);
331
0
        geomColBBOX.osYMax = BuildColName(osYMax);
332
0
        oMapGeomColumnToCoveringBBOXColumn[oColumn.GetName()] =
333
0
            std::move(geomColBBOX);
334
0
        oSetCoveringBBoxColumn.insert(std::move(osBBOXColumn));
335
0
    }
336
0
}
337
338
/************************************************************************/
339
/*                         BuildLayerDefnInit()                         */
340
/************************************************************************/
341
342
bool OGRADBCLayer::BuildLayerDefnInit(bool bCreateStream)
343
0
{
344
0
    CPLAssert(!m_poAdapterLayer);
345
346
0
    m_bLayerDefinitionError = true;
347
0
    m_poAdapterLayer = std::make_unique<OGRArrowArrayToOGRFeatureAdapterLayer>(
348
0
        GetDescription());
349
350
0
    m_statement = std::make_unique<AdbcStatement>();
351
0
    if (!m_osBaseStatement.empty())
352
0
    {
353
0
        OGRADBCError error;
354
0
        if (ADBC_CALL(StatementNew, m_poDS->m_connection.get(),
355
0
                      m_statement.get(), error) != ADBC_STATUS_OK)
356
0
        {
357
0
            CPLError(CE_Failure, CPLE_AppDefined,
358
0
                     "AdbcStatementNew() failed: %s", error.message());
359
0
            return false;
360
0
        }
361
362
0
        if (ADBC_CALL(StatementSetSqlQuery, m_statement.get(),
363
0
                      m_osBaseStatement.c_str(), error) != ADBC_STATUS_OK)
364
0
        {
365
0
            CPLError(CE_Failure, CPLE_AppDefined,
366
0
                     "AdbcStatementSetSqlQuery() failed: %s", error.message());
367
0
            return false;
368
0
        }
369
0
    }
370
371
0
    if (!m_stream && bCreateStream)
372
0
    {
373
0
        OGRADBCError error;
374
0
        m_stream = std::make_unique<OGRArrowArrayStream>();
375
0
        int64_t rows_affected = -1;
376
0
        if (ADBC_CALL(StatementExecuteQuery, m_statement.get(), m_stream->get(),
377
0
                      &rows_affected, error) != ADBC_STATUS_OK)
378
0
        {
379
0
            CPLError(CE_Failure, CPLE_AppDefined,
380
0
                     "AdbcStatementExecuteQuery() failed: %s", error.message());
381
0
            return false;
382
0
        }
383
384
0
        if (m_stream->get_schema(&m_schema) != 0)
385
0
        {
386
0
            CPLError(CE_Failure, CPLE_AppDefined, "get_schema() failed");
387
0
            return false;
388
0
        }
389
0
    }
390
391
0
    m_bLayerDefinitionError = false;
392
0
    return true;
393
0
}
394
395
/************************************************************************/
396
/*                           BuildLayerDefn()                           */
397
/************************************************************************/
398
399
void OGRADBCLayer::BuildLayerDefn()
400
0
{
401
0
    const bool bIsUserLayerDuckDB =
402
0
        !m_bInternalUse &&
403
0
        STARTS_WITH_CI(m_osBaseStatement.c_str(), "SELECT ") &&
404
0
        m_poDS->m_bIsDuckDBDriver;
405
0
    if (!BuildLayerDefnInit(!bIsUserLayerDuckDB))
406
0
        return;
407
408
    // Identify geometry columns for Parquet files, and query them with
409
    // ST_AsWKB() to avoid getting duckdb_spatial own geometry encoding
410
    // (https://github.com/duckdb/duckdb_spatial/blob/a60aa3733741a99c49baaf33390c0f7c8a9598a3/spatial/src/spatial/core/geometry/geometry_serialization.cpp#L11)
411
0
    std::map<std::string, std::unique_ptr<OGRSpatialReference>> oMapGeomColumns;
412
0
    std::map<std::string, OGRwkbGeometryType> oMapType;
413
0
    std::map<std::string, OGREnvelope3D> oMapExtent;
414
0
    std::map<std::string, GeomColBBOX> oMapGeomColumnToCoveringBBOXColumn;
415
416
0
    if (bIsUserLayerDuckDB)
417
0
    {
418
        // Try to read GeoParquet 'geo' metadata
419
0
        std::map<std::string, std::unique_ptr<OGRSpatialReference>>
420
0
            oMapGeomColumnsFromGeoParquet;
421
0
        std::set<std::string> oSetCoveringBBoxColumn;
422
423
0
        std::string osGeoParquetMD;
424
0
        if (!m_poDS->m_osParquetFilename.empty())
425
0
        {
426
0
            auto poMetadataLayer = m_poDS->CreateInternalLayer(
427
0
                std::string("SELECT value FROM parquet_kv_metadata('")
428
0
                    .append(OGRDuplicateCharacter(m_poDS->m_osParquetFilename,
429
0
                                                  '\''))
430
0
                    .append("') WHERE key = 'geo'")
431
0
                    .c_str());
432
0
            if (!poMetadataLayer->GotError())
433
0
            {
434
0
                auto f = std::unique_ptr<OGRFeature>(
435
0
                    poMetadataLayer->GetNextFeature());
436
0
                if (f)
437
0
                {
438
0
                    int nBytes = 0;
439
0
                    const GByte *pabyData = f->GetFieldAsBinary(0, &nBytes);
440
0
                    osGeoParquetMD.assign(
441
0
                        reinterpret_cast<const char *>(pabyData), nBytes);
442
                    // CPLDebug("ADBC", "%s", osGeoParquetMD.c_str());
443
0
                }
444
0
            }
445
0
        }
446
0
        CPLJSONDocument oDoc;
447
0
        if (!osGeoParquetMD.empty() && oDoc.LoadMemory(osGeoParquetMD))
448
0
        {
449
0
            const auto oColumns = oDoc.GetRoot().GetObj("columns");
450
0
            for (const auto &oColumn : oColumns.GetChildren())
451
0
            {
452
0
                if (oColumn.GetString("encoding") == "WKB")
453
0
                {
454
0
                    ParseGeoParquetColumn(oColumn, oMapType, oMapExtent,
455
0
                                          oMapGeomColumnToCoveringBBOXColumn,
456
0
                                          oMapGeomColumnsFromGeoParquet,
457
0
                                          oSetCoveringBBoxColumn);
458
0
                }
459
0
            }
460
0
        }
461
462
0
        auto poDescribeLayer = m_poDS->CreateInternalLayer(
463
0
            std::string("DESCRIBE ").append(m_osBaseStatement).c_str());
464
0
        std::string osNewStatement;
465
0
        bool bNewStatement = false;
466
0
        if (!poDescribeLayer->GotError() &&
467
0
            (m_poDS->m_bIsDuckDBDriver ||
468
             // cppcheck-suppress knownConditionTrueFalse
469
0
             !oMapGeomColumnsFromGeoParquet.empty()))
470
0
        {
471
0
            for (auto &&f : *poDescribeLayer)
472
0
            {
473
0
                const char *pszColName = f->GetFieldAsString("column_name");
474
0
                if (cpl::contains(oSetCoveringBBoxColumn, pszColName))
475
0
                {
476
0
                    bNewStatement = true;
477
0
                    continue;
478
0
                }
479
480
                // f->DumpReadable(stdout);
481
0
                if (osNewStatement.empty())
482
0
                    osNewStatement = "SELECT ";
483
0
                else
484
0
                    osNewStatement += ", ";
485
486
0
                auto oIter = oMapGeomColumnsFromGeoParquet.find(pszColName);
487
0
                if (oIter != oMapGeomColumnsFromGeoParquet.end())
488
0
                {
489
0
                    oMapGeomColumns[pszColName] = std::move(oIter->second);
490
0
                }
491
0
                const char *pszColType = f->GetFieldAsString("column_type");
492
0
                if (STARTS_WITH_CI(pszColType, "GEOMETRY") &&
493
0
                    m_poDS->m_bSpatialLoaded)
494
0
                {
495
0
                    bNewStatement = true;
496
0
                    osNewStatement += "ST_AsWKB(\"";
497
0
                    osNewStatement += OGRDuplicateCharacter(pszColName, '"');
498
0
                    osNewStatement += "\") AS ";
499
0
                    if (oIter == oMapGeomColumnsFromGeoParquet.end())
500
0
                    {
501
0
                        oMapGeomColumns[pszColName] = nullptr;
502
                        // Below is with DuckDB >= 1.5
503
0
                        if (STARTS_WITH_CI(pszColType, "GEOMETRY('EPSG:"))
504
0
                        {
505
0
                            auto poSRS =
506
0
                                std::make_unique<OGRSpatialReference>();
507
0
                            poSRS->SetAxisMappingStrategy(
508
0
                                OAMS_TRADITIONAL_GIS_ORDER);
509
0
                            if (poSRS->importFromEPSG(atoi(
510
0
                                    pszColType + strlen("GEOMETRY('EPSG:"))) ==
511
0
                                OGRERR_NONE)
512
0
                            {
513
0
                                oMapGeomColumns[pszColName] = std::move(poSRS);
514
0
                            }
515
0
                        }
516
0
                    }
517
0
                }
518
0
                osNewStatement += '"';
519
0
                osNewStatement += OGRDuplicateCharacter(pszColName, '"');
520
0
                osNewStatement += '"';
521
0
            }
522
0
            m_osModifiedSelect = osNewStatement;
523
0
            osNewStatement += " FROM (";
524
0
            osNewStatement += m_osBaseStatement;
525
0
            osNewStatement += " )";
526
0
        }
527
528
0
        if (bNewStatement)
529
0
        {
530
            // CPLDebug("ADBC", "%s -> %s", m_osBaseStatement.c_str(), osNewStatement.c_str());
531
0
            if (ReplaceStatement(osNewStatement.c_str()))
532
0
            {
533
0
                m_osModifiedBaseStatement = std::move(osNewStatement);
534
0
            }
535
0
            else
536
0
            {
537
0
                m_osModifiedSelect.clear();
538
0
                oMapGeomColumns.clear();
539
0
            }
540
0
        }
541
542
0
        if (!m_stream)
543
0
        {
544
0
            auto stream = std::make_unique<OGRArrowArrayStream>();
545
0
            if (!GetArrowStreamInternal(stream->get()))
546
0
            {
547
0
                return;
548
0
            }
549
0
            m_stream = std::move(stream);
550
0
            if (m_stream->get_schema(&m_schema) != 0)
551
0
            {
552
0
                CPLError(CE_Failure, CPLE_AppDefined, "get_schema() failed");
553
0
                return;
554
0
            }
555
0
        }
556
0
    }
557
558
0
    for (int i = 0; i < m_schema.n_children; ++i)
559
0
    {
560
0
        const char *pszColName = m_schema.children[i]->name;
561
0
        auto oIter = oMapGeomColumns.find(pszColName);
562
0
        if (oIter != oMapGeomColumns.end())
563
0
        {
564
0
            OGRGeomFieldDefn oGeomFieldDefn(pszColName, oMapType[pszColName]);
565
0
            auto poSRS = std::move(oIter->second).release();
566
0
            if (poSRS)
567
0
            {
568
0
                oGeomFieldDefn.SetSpatialRef(poSRS);
569
0
                poSRS->Release();
570
0
            }
571
0
            m_poAdapterLayer->m_poLayerDefn->AddGeomFieldDefn(&oGeomFieldDefn);
572
573
0
            m_extents.push_back(oMapExtent[pszColName]);
574
0
            m_geomColBBOX.push_back(
575
0
                oMapGeomColumnToCoveringBBOXColumn[pszColName]);
576
0
        }
577
0
        else
578
0
        {
579
0
            m_poAdapterLayer->CreateFieldFromArrowSchema(m_schema.children[i]);
580
0
        }
581
0
    }
582
0
}
583
584
/************************************************************************/
585
/*                           ~OGRADBCLayer()                            */
586
/************************************************************************/
587
588
OGRADBCLayer::~OGRADBCLayer()
589
0
{
590
0
    OGRADBCError error;
591
0
    if (m_statement)
592
0
        ADBC_CALL(StatementRelease, m_statement.get(), error);
593
0
    if (m_schema.release)
594
0
        m_schema.release(&m_schema);
595
0
}
596
597
/************************************************************************/
598
/*                          ReplaceStatement()                          */
599
/************************************************************************/
600
601
bool OGRADBCLayer::ReplaceStatement(const char *pszNewStatement)
602
0
{
603
    // CPLDebug("ADBC", "%s", pszNewStatement);
604
0
    OGRADBCError error;
605
0
    auto statement = std::make_unique<AdbcStatement>();
606
0
    if (ADBC_CALL(StatementNew, m_poDS->m_connection.get(), statement.get(),
607
0
                  error) != ADBC_STATUS_OK)
608
0
    {
609
0
        CPLError(CE_Failure, CPLE_AppDefined, "AdbcStatementNew() failed: %s",
610
0
                 error.message());
611
0
        ADBC_CALL(StatementRelease, statement.get(), error);
612
0
    }
613
0
    else if (ADBC_CALL(StatementSetSqlQuery, statement.get(), pszNewStatement,
614
0
                       error) != ADBC_STATUS_OK)
615
0
    {
616
0
        CPLError(CE_Failure, CPLE_AppDefined,
617
0
                 "AdbcStatementSetSqlQuery() failed: %s", error.message());
618
0
        error.clear();
619
0
        ADBC_CALL(StatementRelease, statement.get(), error);
620
0
    }
621
0
    else
622
0
    {
623
0
        auto stream = std::make_unique<OGRArrowArrayStream>();
624
0
        int64_t rows_affected = -1;
625
0
        ArrowSchema newSchema;
626
0
        memset(&newSchema, 0, sizeof(newSchema));
627
0
        if (ADBC_CALL(StatementExecuteQuery, statement.get(), stream->get(),
628
0
                      &rows_affected, error) != ADBC_STATUS_OK)
629
0
        {
630
0
            CPLError(CE_Failure, CPLE_AppDefined,
631
0
                     "AdbcStatementExecuteQuery() failed: %s", error.message());
632
0
            error.clear();
633
0
            ADBC_CALL(StatementRelease, statement.get(), error);
634
0
        }
635
0
        else if (stream->get_schema(&newSchema) != 0)
636
0
        {
637
0
            CPLError(CE_Failure, CPLE_AppDefined, "get_schema() failed");
638
0
            ADBC_CALL(StatementRelease, statement.get(), error);
639
0
        }
640
0
        else
641
0
        {
642
0
            if (m_schema.release)
643
0
                m_schema.release(&m_schema);
644
0
            memcpy(&m_schema, &newSchema, sizeof(newSchema));
645
646
0
            if (m_statement)
647
0
                ADBC_CALL(StatementRelease, m_statement.get(), error);
648
0
            m_statement = std::move(statement);
649
650
0
            m_stream = std::move(stream);
651
652
0
            return true;
653
0
        }
654
0
    }
655
0
    return false;
656
0
}
657
658
/************************************************************************/
659
/*                         GetNextRawFeature()                          */
660
/************************************************************************/
661
662
OGRFeature *OGRADBCLayer::GetNextRawFeature()
663
0
{
664
0
    if (!m_poAdapterLayer)
665
0
        BuildLayerDefn();
666
0
    RunDeferredCreation();
667
668
0
    if (m_bEOF || m_bLayerDefinitionError)
669
0
        return nullptr;
670
671
0
    if (m_nIdx == m_poAdapterLayer->m_apoFeatures.size())
672
0
    {
673
0
        m_nIdx = 0;
674
0
        m_poAdapterLayer->m_apoFeatures.clear();
675
676
0
        struct ArrowArray array;
677
0
        for (int i = 0; i < 2; ++i)
678
0
        {
679
0
            if (!m_stream)
680
0
            {
681
0
                auto stream = std::make_unique<OGRArrowArrayStream>();
682
0
                if (!GetArrowStreamInternal(stream->get()))
683
0
                {
684
0
                    m_bEOF = true;
685
0
                    return nullptr;
686
0
                }
687
0
                m_stream = std::move(stream);
688
0
            }
689
690
0
            memset(&array, 0, sizeof(array));
691
0
            if (m_stream->get_next(&array) != 0)
692
0
            {
693
0
                if (m_bNextStreamUsageMaybeInvalid)
694
0
                {
695
0
                    m_bNextStreamUsageMaybeInvalid = false;
696
0
                    m_stream.reset();
697
0
                }
698
0
                else
699
0
                {
700
0
                    m_bEOF = true;
701
0
                    return nullptr;
702
0
                }
703
0
            }
704
0
            else
705
0
            {
706
0
                break;
707
0
            }
708
0
        }
709
0
        m_bGetNextArrayHasRun = true;
710
0
        const bool bOK =
711
0
            array.length
712
0
                ? m_poAdapterLayer->WriteArrowBatch(&m_schema, &array, nullptr)
713
0
                : false;
714
0
        if (array.release)
715
0
            array.release(&array);
716
0
        if (!bOK)
717
0
        {
718
0
            m_bEOF = true;
719
0
            return nullptr;
720
0
        }
721
0
    }
722
723
0
    auto poFeature = m_poAdapterLayer->m_apoFeatures[m_nIdx++].release();
724
0
    const int nGeomFieldCount =
725
0
        m_poAdapterLayer->m_poLayerDefn->GetFieldCount();
726
0
    for (int i = 0; i < nGeomFieldCount; ++i)
727
0
    {
728
0
        auto poGeom = poFeature->GetGeomFieldRef(i);
729
0
        if (poGeom)
730
0
            poGeom->assignSpatialReference(
731
0
                m_poAdapterLayer->m_poLayerDefn->GetGeomFieldDefn(i)
732
0
                    ->GetSpatialRef());
733
0
    }
734
0
    if (m_osFIDColName.empty())
735
0
        poFeature->SetFID(m_nFeatureID++);
736
0
    else
737
0
        poFeature->SetFID(
738
0
            poFeature->GetFieldAsInteger64(m_osFIDColName.c_str()));
739
0
    return poFeature;
740
0
}
741
742
/************************************************************************/
743
/*                            ResetReading()                            */
744
/************************************************************************/
745
746
void OGRADBCLayer::ResetReading()
747
0
{
748
0
    if (m_nIdx > 0 || m_bEOF)
749
0
    {
750
0
        m_poAdapterLayer->m_apoFeatures.clear();
751
0
        m_stream.reset();
752
0
        m_bEOF = false;
753
0
        m_nIdx = 0;
754
0
        m_nFeatureID = 0;
755
0
        m_bGetNextArrayHasRun = false;
756
0
    }
757
0
}
758
759
/************************************************************************/
760
/*                             IGetExtent()                             */
761
/************************************************************************/
762
763
OGRErr OGRADBCLayer::IGetExtent(int iGeomField, OGREnvelope *psExtent,
764
                                bool bForce)
765
0
{
766
0
    if (!m_poAdapterLayer)
767
0
        BuildLayerDefn();
768
769
0
    *psExtent = m_extents[iGeomField];
770
0
    if (psExtent->IsInit())
771
0
        return OGRERR_NONE;
772
773
0
    return OGRLayer::IGetExtent(iGeomField, psExtent, bForce);
774
0
}
775
776
/************************************************************************/
777
/*                            IGetExtent3D()                            */
778
/************************************************************************/
779
780
OGRErr OGRADBCLayer::IGetExtent3D(int iGeomField, OGREnvelope3D *psExtent,
781
                                  bool bForce)
782
0
{
783
0
    if (!m_poAdapterLayer)
784
0
        BuildLayerDefn();
785
786
0
    *psExtent = m_extents[iGeomField];
787
0
    if (psExtent->IsInit())
788
0
        return OGRERR_NONE;
789
790
0
    return OGRLayer::IGetExtent3D(iGeomField, psExtent, bForce);
791
0
}
792
793
/************************************************************************/
794
/*                        GetCurrentStatement()                         */
795
/************************************************************************/
796
797
std::string OGRADBCLayer::GetCurrentStatement() const
798
0
{
799
0
    if (m_poDS->m_bIsDuckDBDriver && !m_osModifiedSelect.empty() &&
800
0
        STARTS_WITH_CI(m_osBaseStatement.c_str(), "SELECT * FROM ") &&
801
0
        (!m_osAttributeFilter.empty() ||
802
0
         (m_poFilterGeom &&
803
0
          (!m_geomColBBOX[m_iGeomFieldFilter].osXMin.empty() ||
804
0
           m_poDS->m_bSpatialLoaded))))
805
0
    {
806
0
        std::string osStatement(m_osModifiedSelect);
807
0
        osStatement.append(" FROM (")
808
0
            .append(m_osBaseStatement)
809
0
            .append(") WHERE ");
810
811
0
        bool bAddAnd = false;
812
0
        if (m_poFilterGeom)
813
0
        {
814
0
            const double dfMinX = std::isinf(m_sFilterEnvelope.MinX)
815
0
                                      ? -std::numeric_limits<double>::max()
816
0
                                      : m_sFilterEnvelope.MinX;
817
0
            const double dfMinY = std::isinf(m_sFilterEnvelope.MinY)
818
0
                                      ? -std::numeric_limits<double>::max()
819
0
                                      : m_sFilterEnvelope.MinY;
820
0
            const double dfMaxX = std::isinf(m_sFilterEnvelope.MaxX)
821
0
                                      ? std::numeric_limits<double>::max()
822
0
                                      : m_sFilterEnvelope.MaxX;
823
0
            const double dfMaxY = std::isinf(m_sFilterEnvelope.MaxY)
824
0
                                      ? std::numeric_limits<double>::max()
825
0
                                      : m_sFilterEnvelope.MaxY;
826
0
            if (!m_geomColBBOX[m_iGeomFieldFilter].osXMin.empty())
827
0
            {
828
0
                bAddAnd = true;
829
0
                osStatement.append(m_geomColBBOX[m_iGeomFieldFilter].osXMin)
830
0
                    .append(" <= ")
831
0
                    .append(CPLSPrintf("%.17g", dfMaxX))
832
0
                    .append(" AND ")
833
0
                    .append(m_geomColBBOX[m_iGeomFieldFilter].osXMax)
834
0
                    .append(" >= ")
835
0
                    .append(CPLSPrintf("%.17g", dfMinX))
836
0
                    .append(" AND ")
837
0
                    .append(m_geomColBBOX[m_iGeomFieldFilter].osYMin)
838
0
                    .append(" <= ")
839
0
                    .append(CPLSPrintf("%.17g", dfMaxY))
840
0
                    .append(" AND ")
841
0
                    .append(m_geomColBBOX[m_iGeomFieldFilter].osYMax)
842
0
                    .append(" >= ")
843
0
                    .append(CPLSPrintf("%.17g", dfMinY));
844
0
            }
845
0
            if (m_poDS->m_bSpatialLoaded)
846
0
            {
847
0
                if (bAddAnd)
848
0
                    osStatement.append(" AND ");
849
0
                bAddAnd = true;
850
0
                osStatement.append("ST_Intersects(\"")
851
0
                    .append(OGRDuplicateCharacter(
852
0
                        m_poAdapterLayer->m_poLayerDefn
853
0
                            ->GetGeomFieldDefn(m_iGeomFieldFilter)
854
0
                            ->GetNameRef(),
855
0
                        '"'))
856
0
                    .append(CPLSPrintf(
857
0
                        "\", ST_MakeEnvelope(%.17g,%.17g,%.17g,%.17g))", dfMinX,
858
0
                        dfMinY, dfMaxX, dfMaxY));
859
0
            }
860
0
        }
861
0
        if (!m_osAttributeFilter.empty())
862
0
        {
863
0
            if (bAddAnd)
864
0
                osStatement.append(" AND ");
865
0
            osStatement.append("(");
866
0
            osStatement.append(m_osAttributeFilter);
867
0
            osStatement.append(")");
868
0
        }
869
870
0
        return osStatement;
871
0
    }
872
0
    else
873
0
    {
874
0
        return m_osModifiedBaseStatement;
875
0
    }
876
0
}
877
878
/************************************************************************/
879
/*                          UpdateStatement()                           */
880
/************************************************************************/
881
882
bool OGRADBCLayer::UpdateStatement()
883
0
{
884
0
    return ReplaceStatement(GetCurrentStatement().c_str());
885
0
}
886
887
/************************************************************************/
888
/*                         SetAttributeFilter()                         */
889
/************************************************************************/
890
891
OGRErr OGRADBCLayer::SetAttributeFilter(const char *pszFilter)
892
0
{
893
0
    if (!m_osModifiedSelect.empty() && m_poDS->m_bIsDuckDBDriver &&
894
0
        STARTS_WITH_CI(m_osBaseStatement.c_str(), "SELECT * FROM "))
895
0
    {
896
0
        m_osAttributeFilter = pszFilter ? pszFilter : "";
897
0
        return UpdateStatement() ? OGRERR_NONE : OGRERR_FAILURE;
898
0
    }
899
0
    else
900
0
    {
901
0
        return OGRLayer::SetAttributeFilter(pszFilter);
902
0
    }
903
0
}
904
905
/************************************************************************/
906
/*                         ISetSpatialFilter()                          */
907
/************************************************************************/
908
909
OGRErr OGRADBCLayer::ISetSpatialFilter(int iGeomField,
910
                                       const OGRGeometry *poGeomIn)
911
912
0
{
913
0
    if (iGeomField < GetLayerDefn()->GetGeomFieldCount())
914
0
    {
915
0
        m_iGeomFieldFilter = iGeomField;
916
0
        if (InstallFilter(poGeomIn))
917
0
            ResetReading();
918
0
        UpdateStatement();
919
0
    }
920
0
    return OGRERR_NONE;
921
0
}
922
923
/************************************************************************/
924
/*                           TestCapability()                           */
925
/************************************************************************/
926
927
int OGRADBCLayer::TestCapability(const char *pszCap) const
928
0
{
929
0
    if (!m_poAdapterLayer)
930
0
        const_cast<OGRADBCLayer *>(this)->BuildLayerDefn();
931
932
0
    if (EQUAL(pszCap, OLCFastGetArrowStream))
933
0
    {
934
0
        return !m_poFilterGeom && !m_poAttrQuery && m_osAttributeFilter.empty();
935
0
    }
936
0
    else if (EQUAL(pszCap, OLCFastFeatureCount))
937
0
    {
938
0
        return !m_poFilterGeom && !m_poAttrQuery &&
939
0
               m_osAttributeFilter.empty() && m_bIsParquetLayer;
940
0
    }
941
0
    else if (EQUAL(pszCap, OLCFastGetExtent))
942
0
    {
943
0
        return !m_extents.empty() && m_extents[0].IsInit();
944
0
    }
945
0
    else if (EQUAL(pszCap, OLCFastSpatialFilter) && m_iGeomFieldFilter >= 0 &&
946
0
             m_iGeomFieldFilter < GetLayerDefn()->GetGeomFieldCount())
947
0
    {
948
0
        if (m_poDS->m_bSpatialLoaded && m_poDS->m_bIsDuckDBDataset)
949
0
        {
950
0
            const char *pszGeomColName =
951
0
                m_poAdapterLayer->m_poLayerDefn
952
0
                    ->GetGeomFieldDefn(m_iGeomFieldFilter)
953
0
                    ->GetNameRef();
954
0
            auto poTmpLayer = m_poDS->CreateInternalLayer(CPLSPrintf(
955
0
                "SELECT 1 FROM sqlite_master WHERE tbl_name = '%s' AND type = "
956
0
                "'index' AND (sql LIKE '%%USING RTREE (%s)%%' OR sql LIKE "
957
0
                "'%%USING RTREE (\"%s\")%%')",
958
0
                OGRDuplicateCharacter(GetDescription(), '\'').c_str(),
959
0
                pszGeomColName,
960
0
                OGRDuplicateCharacter(pszGeomColName, '"').c_str()));
961
0
            return !poTmpLayer->GotError() &&
962
0
                   std::unique_ptr<OGRFeature>(poTmpLayer->GetNextFeature());
963
0
        }
964
0
        else if (!m_geomColBBOX[m_iGeomFieldFilter].osXMin.empty())
965
0
        {
966
            // Let's assume that the presence of a geometry bounding box
967
            // column is sufficient enough to pretend to have fast spatial
968
            // filter capabilities
969
0
            return true;
970
0
        }
971
0
    }
972
0
    else if (EQUAL(pszCap, OLCStringsAsUTF8))
973
0
        return true;
974
975
0
    return false;
976
0
}
977
978
/************************************************************************/
979
/*                             GetDataset()                             */
980
/************************************************************************/
981
982
GDALDataset *OGRADBCLayer::GetDataset()
983
0
{
984
0
    return m_poDS;
985
0
}
986
987
/************************************************************************/
988
/*                           GetArrowStream()                           */
989
/************************************************************************/
990
991
bool OGRADBCLayer::GetArrowStream(struct ArrowArrayStream *out_stream,
992
                                  CSLConstList papszOptions)
993
0
{
994
0
    if (!m_poAdapterLayer)
995
0
        BuildLayerDefn();
996
997
0
    if (m_poFilterGeom || m_poAttrQuery ||
998
0
        CPLFetchBool(papszOptions, GAS_OPT_DATETIME_AS_STRING, false))
999
0
    {
1000
0
        return OGRLayer::GetArrowStream(out_stream, papszOptions);
1001
0
    }
1002
1003
0
    if (m_stream)
1004
0
    {
1005
0
        memcpy(out_stream, m_stream->get(), sizeof(*out_stream));
1006
0
        memset(m_stream->get(), 0, sizeof(*out_stream));
1007
0
        m_stream.reset();
1008
0
    }
1009
1010
0
    return GetArrowStreamInternal(out_stream);
1011
0
}
1012
1013
/************************************************************************/
1014
/*                       GetArrowStreamInternal()                       */
1015
/************************************************************************/
1016
1017
bool OGRADBCLayer::GetArrowStreamInternal(struct ArrowArrayStream *out_stream)
1018
0
{
1019
0
    OGRADBCError error;
1020
0
    int64_t rows_affected = -1;
1021
0
    if (ADBC_CALL(StatementExecuteQuery, m_statement.get(), out_stream,
1022
0
                  &rows_affected, error) != ADBC_STATUS_OK)
1023
0
    {
1024
0
        CPLError(CE_Failure, CPLE_AppDefined,
1025
0
                 "AdbcStatementExecuteQuery() failed: %s", error.message());
1026
0
        return false;
1027
0
    }
1028
1029
0
    return true;
1030
0
}
1031
1032
/************************************************************************/
1033
/*                   GetFeatureCountSelectCountStar()                   */
1034
/************************************************************************/
1035
1036
GIntBig OGRADBCLayer::GetFeatureCountSelectCountStar()
1037
0
{
1038
0
    const std::string osCurStatement = GetCurrentStatement();
1039
1040
    // Cf https://github.com/duckdb/duckdb/issues/21384
1041
    // and https://github.com/apache/arrow-adbc/blob/da58c591ed89b29c9096e4ebc0fe99d369e2bc88/docs/source/cpp/concurrency.rst
1042
0
    m_bNextStreamUsageMaybeInvalid = true;
1043
1044
0
    auto poCountLayer =
1045
0
        m_poDS->CreateInternalLayer(std::string("SELECT COUNT(*) FROM (")
1046
0
                                        .append(osCurStatement)
1047
0
                                        .append(")")
1048
0
                                        .c_str());
1049
0
    if (!poCountLayer->GotError() &&
1050
0
        poCountLayer->GetLayerDefn()->GetFieldCount() == 1)
1051
0
    {
1052
0
        auto poFeature =
1053
0
            std::unique_ptr<OGRFeature>(poCountLayer->GetNextFeature());
1054
0
        if (poFeature)
1055
0
            return poFeature->GetFieldAsInteger64(0);
1056
0
    }
1057
0
    if (m_bGetNextArrayHasRun)
1058
0
    {
1059
0
        m_bGetNextArrayHasRun = false;
1060
0
        return GetFeatureCountSelectCountStar();
1061
0
    }
1062
1063
0
    return -1;
1064
0
}
1065
1066
/************************************************************************/
1067
/*                        GetFeatureCountArrow()                        */
1068
/************************************************************************/
1069
1070
GIntBig OGRADBCLayer::GetFeatureCountArrow()
1071
0
{
1072
0
    if (m_nIdx > 0 || m_bEOF)
1073
0
        m_stream.reset();
1074
1075
0
    if (!m_stream)
1076
0
    {
1077
0
        auto stream = std::make_unique<OGRArrowArrayStream>();
1078
0
        if (!GetArrowStreamInternal(stream->get()))
1079
0
        {
1080
0
            return -1;
1081
0
        }
1082
0
        m_stream = std::move(stream);
1083
0
    }
1084
1085
0
    GIntBig nTotal = 0;
1086
0
    while (true)
1087
0
    {
1088
0
        struct ArrowArray array;
1089
0
        memset(&array, 0, sizeof(array));
1090
0
        if (m_stream->get_next(&array) != 0)
1091
0
        {
1092
0
            if (m_bNextStreamUsageMaybeInvalid)
1093
0
            {
1094
0
                m_bNextStreamUsageMaybeInvalid = false;
1095
0
                auto stream = std::make_unique<OGRArrowArrayStream>();
1096
0
                if (!GetArrowStreamInternal(stream->get()))
1097
0
                {
1098
0
                    return -1;
1099
0
                }
1100
0
                m_stream = std::move(stream);
1101
0
                if (m_stream->get_next(&array) != 0)
1102
0
                {
1103
0
                    m_stream.reset();
1104
0
                    return -1;
1105
0
                }
1106
0
            }
1107
0
            else
1108
0
            {
1109
0
                m_stream.reset();
1110
0
                return -1;
1111
0
            }
1112
0
        }
1113
0
        m_bGetNextArrayHasRun = true;
1114
0
        const bool bStop = array.length == 0;
1115
0
        nTotal += array.length;
1116
0
        if (array.release)
1117
0
            array.release(&array);
1118
0
        if (bStop)
1119
0
            break;
1120
0
    }
1121
0
    m_stream.reset();
1122
0
    return nTotal;
1123
0
}
1124
1125
/************************************************************************/
1126
/*                          GetFeatureCount()                           */
1127
/************************************************************************/
1128
1129
GIntBig OGRADBCLayer::GetFeatureCount(int bForce)
1130
0
{
1131
0
    if (!m_poAdapterLayer)
1132
0
        BuildLayerDefn();
1133
0
    if (m_bLayerDefinitionError)
1134
0
        return 0;
1135
1136
0
    if (m_poFilterGeom || m_poAttrQuery || !m_osAttributeFilter.empty())
1137
0
    {
1138
0
        if (!m_osModifiedSelect.empty() &&
1139
0
            STARTS_WITH_CI(m_osBaseStatement.c_str(), "SELECT * FROM ") &&
1140
0
            (!m_poFilterGeom ||
1141
0
             !m_geomColBBOX[m_iGeomFieldFilter].osXMin.empty() ||
1142
0
             m_poDS->m_bSpatialLoaded))
1143
0
        {
1144
0
            auto nCount = GetFeatureCountSelectCountStar();
1145
0
            if (nCount >= 0)
1146
0
                return nCount;
1147
0
        }
1148
1149
0
        return OGRLayer::GetFeatureCount(bForce);
1150
0
    }
1151
1152
0
    if (m_bIsParquetLayer)
1153
0
    {
1154
0
        return GetFeatureCountParquet();
1155
0
    }
1156
1157
0
    return GetFeatureCountArrow();
1158
0
}
1159
1160
/************************************************************************/
1161
/*                       GetFeatureCountParquet()                       */
1162
/************************************************************************/
1163
1164
GIntBig OGRADBCLayer::GetFeatureCountParquet()
1165
0
{
1166
    // Cf https://github.com/duckdb/duckdb/issues/21384
1167
    // and https://github.com/apache/arrow-adbc/blob/da58c591ed89b29c9096e4ebc0fe99d369e2bc88/docs/source/cpp/concurrency.rst
1168
0
    m_bNextStreamUsageMaybeInvalid = true;
1169
1170
0
    const std::string osSQL(CPLSPrintf(
1171
0
        "SELECT CAST(SUM(num_rows) AS BIGINT) FROM parquet_file_metadata('%s')",
1172
0
        OGRDuplicateCharacter(m_poDS->m_osParquetFilename, '\'').c_str()));
1173
0
    auto poCountLayer = m_poDS->CreateInternalLayer(osSQL.c_str());
1174
0
    if (!poCountLayer->GotError() &&
1175
0
        poCountLayer->GetLayerDefn()->GetFieldCount() == 1)
1176
0
    {
1177
0
        auto poFeature =
1178
0
            std::unique_ptr<OGRFeature>(poCountLayer->GetNextFeature());
1179
0
        if (poFeature)
1180
0
            return poFeature->GetFieldAsInteger64(0);
1181
0
    }
1182
0
    if (m_bGetNextArrayHasRun)
1183
0
    {
1184
0
        m_bGetNextArrayHasRun = false;
1185
0
        return GetFeatureCountParquet();
1186
0
    }
1187
1188
0
    return -1;
1189
0
}
1190
1191
#undef ADBC_CALL