Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GeoPackage Translator
4 : * Purpose: Implements OGRGeoPackageLayer class
5 : * Author: Paul Ramsey <pramsey@boundlessgeo.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com>
9 : * Copyright (c) 2014, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "ogr_geopackage.h"
15 : #include "ogrgeopackageutility.h"
16 : #include "ogrsqliteutility.h"
17 : #include "ogr_p.h"
18 : #include "ogr_recordbatch.h"
19 : #include "ograrrowarrayhelper.h"
20 :
21 : /************************************************************************/
22 : /* OGRGeoPackageLayer() */
23 : /************************************************************************/
24 :
25 4339 : OGRGeoPackageLayer::OGRGeoPackageLayer(GDALGeoPackageDataset *poDS)
26 4339 : : m_poDS(poDS)
27 : {
28 4339 : }
29 :
30 : /************************************************************************/
31 : /* ~OGRGeoPackageLayer() */
32 : /************************************************************************/
33 :
34 4339 : OGRGeoPackageLayer::~OGRGeoPackageLayer()
35 : {
36 :
37 4339 : CPLFree(m_pszFidColumn);
38 :
39 4339 : if (m_poQueryStatement)
40 800 : sqlite3_finalize(m_poQueryStatement);
41 :
42 4339 : if (m_poFeatureDefn)
43 4339 : m_poFeatureDefn->Release();
44 4339 : }
45 :
46 : /************************************************************************/
47 : /* ResetReading() */
48 : /************************************************************************/
49 :
50 24583 : void OGRGeoPackageLayer::ResetReading()
51 :
52 : {
53 24583 : ClearStatement();
54 24583 : m_iNextShapeId = 0;
55 24583 : m_bEOF = false;
56 24583 : }
57 :
58 : /************************************************************************/
59 : /* ClearStatement() */
60 : /************************************************************************/
61 :
62 25596 : void OGRGeoPackageLayer::ClearStatement()
63 :
64 : {
65 25596 : if (m_poQueryStatement != nullptr)
66 : {
67 504 : CPLDebug("GPKG", "finalize %p", m_poQueryStatement);
68 504 : sqlite3_finalize(m_poQueryStatement);
69 504 : m_poQueryStatement = nullptr;
70 : }
71 25596 : }
72 :
73 : /************************************************************************/
74 : /* GetNextFeature() */
75 : /************************************************************************/
76 :
77 12752 : OGRFeature *OGRGeoPackageLayer::GetNextFeature()
78 :
79 : {
80 12752 : if (m_bEOF)
81 5 : return nullptr;
82 :
83 12747 : if (m_poQueryStatement == nullptr)
84 : {
85 649 : ResetStatement();
86 649 : if (m_poQueryStatement == nullptr)
87 1 : return nullptr;
88 : }
89 :
90 : for (; true;)
91 : {
92 : /* --------------------------------------------------------------------
93 : */
94 : /* Fetch a record (unless otherwise instructed) */
95 : /* --------------------------------------------------------------------
96 : */
97 12751 : if (m_bDoStep)
98 : {
99 12300 : int rc = sqlite3_step(m_poQueryStatement);
100 12300 : if (rc != SQLITE_ROW)
101 : {
102 318 : if (rc != SQLITE_DONE)
103 : {
104 0 : sqlite3_reset(m_poQueryStatement);
105 0 : CPLError(CE_Failure, CPLE_AppDefined,
106 : "In GetNextRawFeature(): sqlite3_step() : %s",
107 0 : sqlite3_errmsg(m_poDS->GetDB()));
108 : }
109 :
110 318 : ClearStatement();
111 318 : m_bEOF = true;
112 :
113 318 : return nullptr;
114 : }
115 : }
116 : else
117 : {
118 451 : m_bDoStep = true;
119 : }
120 :
121 12433 : OGRFeature *poFeature = TranslateFeature(m_poQueryStatement);
122 :
123 34731 : if ((m_poFilterGeom == nullptr ||
124 24861 : FilterGeometry(poFeature->GetGeomFieldRef(m_iGeomFieldFilter))) &&
125 12428 : (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
126 12428 : return poFeature;
127 :
128 5 : delete poFeature;
129 5 : }
130 : }
131 :
132 : /************************************************************************/
133 : /* ParseDateField() */
134 : /************************************************************************/
135 :
136 452 : bool OGRGeoPackageLayer::ParseDateField(sqlite3_stmt *hStmt, int iRawField,
137 : int nSqlite3ColType, OGRField *psField,
138 : const OGRFieldDefn *poFieldDefn,
139 : GIntBig nFID)
140 : {
141 452 : if (nSqlite3ColType == SQLITE_TEXT)
142 : {
143 : const char *pszTxt = reinterpret_cast<const char *>(
144 451 : sqlite3_column_text(hStmt, iRawField));
145 451 : return ParseDateField(pszTxt, psField, poFieldDefn, nFID);
146 : }
147 : else
148 : {
149 1 : constexpr int line = __LINE__;
150 1 : if (!m_poDS->m_oSetGPKGLayerWarnings[line])
151 : {
152 1 : CPLError(CE_Warning, CPLE_AppDefined,
153 : "Unexpected data type for record " CPL_FRMT_GIB
154 : " in column %s",
155 : nFID, poFieldDefn->GetNameRef());
156 1 : m_poDS->m_oSetGPKGLayerWarnings[line] = true;
157 : }
158 1 : return false;
159 : }
160 : }
161 :
162 502 : bool OGRGeoPackageLayer::ParseDateField(const char *pszTxt, OGRField *psField,
163 : const OGRFieldDefn *poFieldDefn,
164 : GIntBig nFID)
165 : {
166 502 : if (pszTxt == nullptr)
167 : {
168 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
169 0 : sqlite3_errmsg(m_poDS->GetDB()));
170 0 : return false;
171 : }
172 502 : const size_t nLen = strlen(pszTxt);
173 : // nominal format: "YYYY-MM-DD" (10 characters)
174 502 : const bool bNominalFormat =
175 501 : (nLen == 10 && pszTxt[4] == '-' && pszTxt[7] == '-' &&
176 500 : static_cast<unsigned>(pszTxt[0] - '0') <= 9 &&
177 500 : static_cast<unsigned>(pszTxt[1] - '0') <= 9 &&
178 500 : static_cast<unsigned>(pszTxt[2] - '0') <= 9 &&
179 500 : static_cast<unsigned>(pszTxt[3] - '0') <= 9 &&
180 500 : static_cast<unsigned>(pszTxt[5] - '0') <= 9 &&
181 500 : static_cast<unsigned>(pszTxt[6] - '0') <= 9 &&
182 1503 : static_cast<unsigned>(pszTxt[8] - '0') <= 9 &&
183 500 : static_cast<unsigned>(pszTxt[9] - '0') <= 9);
184 :
185 502 : bool bError = false;
186 502 : if (bNominalFormat)
187 : {
188 500 : psField->Date.Year = static_cast<GUInt16>(
189 500 : ((((pszTxt[0] - '0') * 10 + (pszTxt[1] - '0')) * 10) +
190 500 : (pszTxt[2] - '0')) *
191 500 : 10 +
192 500 : (pszTxt[3] - '0'));
193 500 : psField->Date.Month =
194 500 : static_cast<GByte>((pszTxt[5] - '0') * 10 + (pszTxt[6] - '0'));
195 500 : psField->Date.Day =
196 500 : static_cast<GByte>((pszTxt[8] - '0') * 10 + (pszTxt[9] - '0'));
197 500 : psField->Date.Hour = 0;
198 500 : psField->Date.Minute = 0;
199 500 : psField->Date.Second = 0.0f;
200 500 : psField->Date.TZFlag = 0;
201 500 : if (psField->Date.Month == 0 || psField->Date.Month > 12 ||
202 500 : psField->Date.Day == 0 || psField->Date.Day > 31)
203 : {
204 0 : bError = true;
205 : }
206 : }
207 2 : else if (OGRParseDate(pszTxt, psField, OGRPARSEDATE_OPTION_LAX))
208 : {
209 1 : constexpr int line = __LINE__;
210 1 : if (!m_poDS->m_oSetGPKGLayerWarnings[line])
211 : {
212 1 : CPLError(CE_Warning, CPLE_AppDefined,
213 : "Non-conformant content for record " CPL_FRMT_GIB
214 : " in column %s, %s, "
215 : "successfully parsed",
216 : nFID, poFieldDefn->GetNameRef(), pszTxt);
217 1 : m_poDS->m_oSetGPKGLayerWarnings[line] = true;
218 : }
219 : }
220 : else
221 : {
222 1 : bError = true;
223 : }
224 :
225 502 : if (bError)
226 : {
227 1 : OGR_RawField_SetUnset(psField);
228 1 : constexpr int line = __LINE__;
229 1 : if (!m_poDS->m_oSetGPKGLayerWarnings[line])
230 : {
231 1 : CPLError(CE_Warning, CPLE_AppDefined,
232 : "Invalid content for record " CPL_FRMT_GIB
233 : " in column %s: %s",
234 : nFID, poFieldDefn->GetNameRef(), pszTxt);
235 1 : m_poDS->m_oSetGPKGLayerWarnings[line] = true;
236 : }
237 1 : return false;
238 : }
239 :
240 501 : return true;
241 : }
242 :
243 : /************************************************************************/
244 : /* ParseDateTimeField() */
245 : /************************************************************************/
246 :
247 439 : bool OGRGeoPackageLayer::ParseDateTimeField(sqlite3_stmt *hStmt, int iRawField,
248 : int nSqlite3ColType,
249 : OGRField *psField,
250 : const OGRFieldDefn *poFieldDefn,
251 : GIntBig nFID)
252 : {
253 439 : if (nSqlite3ColType == SQLITE_TEXT)
254 : {
255 : const char *pszTxt = reinterpret_cast<const char *>(
256 438 : sqlite3_column_text(hStmt, iRawField));
257 438 : return ParseDateTimeField(pszTxt, psField, poFieldDefn, nFID);
258 : }
259 : else
260 : {
261 1 : constexpr int line = __LINE__;
262 1 : if (!m_poDS->m_oSetGPKGLayerWarnings[line])
263 : {
264 1 : CPLError(CE_Warning, CPLE_AppDefined,
265 : "Unexpected data type for record " CPL_FRMT_GIB
266 : " in column %s",
267 : nFID, poFieldDefn->GetNameRef());
268 1 : m_poDS->m_oSetGPKGLayerWarnings[line] = true;
269 : }
270 1 : return false;
271 : }
272 : }
273 :
274 493 : bool OGRGeoPackageLayer::ParseDateTimeField(const char *pszTxt,
275 : OGRField *psField,
276 : const OGRFieldDefn *poFieldDefn,
277 : GIntBig nFID)
278 : {
279 493 : if (pszTxt == nullptr)
280 : {
281 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
282 0 : sqlite3_errmsg(m_poDS->GetDB()));
283 0 : return false;
284 : }
285 :
286 493 : const size_t nLen = strlen(pszTxt);
287 :
288 493 : if (OGRParseDateTimeYYYYMMDDTHHMMSSsssZ(pszTxt, nLen, psField) ||
289 498 : OGRParseDateTimeYYYYMMDDTHHMMSSZ(pszTxt, nLen, psField) ||
290 5 : OGRParseDateTimeYYYYMMDDTHHMMZ(pszTxt, nLen, psField))
291 : {
292 : // nominal format is YYYYMMDDTHHMMSSsssZ before GeoPackage 1.4
293 : // GeoPackage 1.4 also accepts omission of seconds and milliseconds
294 : }
295 4 : else if (OGRParseDate(pszTxt, psField, OGRPARSEDATE_OPTION_LAX))
296 : {
297 3 : constexpr int line = __LINE__;
298 3 : if (!m_poDS->m_oSetGPKGLayerWarnings[line])
299 : {
300 2 : CPLError(CE_Warning, CPLE_AppDefined,
301 : "Non-conformant content for record " CPL_FRMT_GIB
302 : " in column %s, %s, "
303 : "successfully parsed",
304 : nFID, poFieldDefn->GetNameRef(), pszTxt);
305 2 : m_poDS->m_oSetGPKGLayerWarnings[line] = true;
306 : }
307 : }
308 : else
309 : {
310 1 : OGR_RawField_SetUnset(psField);
311 1 : constexpr int line = __LINE__;
312 1 : if (!m_poDS->m_oSetGPKGLayerWarnings[line])
313 : {
314 1 : CPLError(CE_Warning, CPLE_AppDefined,
315 : "Invalid content for record " CPL_FRMT_GIB
316 : " in column %s: %s",
317 : nFID, poFieldDefn->GetNameRef(), pszTxt);
318 1 : m_poDS->m_oSetGPKGLayerWarnings[line] = true;
319 : }
320 1 : return false;
321 : }
322 :
323 492 : return true;
324 : }
325 :
326 : /************************************************************************/
327 : /* TranslateFeature() */
328 : /************************************************************************/
329 :
330 13709 : OGRFeature *OGRGeoPackageLayer::TranslateFeature(sqlite3_stmt *hStmt)
331 :
332 : {
333 : /* -------------------------------------------------------------------- */
334 : /* Create a feature from the current result. */
335 : /* -------------------------------------------------------------------- */
336 13709 : OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);
337 :
338 : /* -------------------------------------------------------------------- */
339 : /* Set FID if we have a column to set it from. */
340 : /* -------------------------------------------------------------------- */
341 13709 : if (m_iFIDCol >= 0)
342 : {
343 12059 : poFeature->SetFID(sqlite3_column_int64(hStmt, m_iFIDCol));
344 12059 : if (m_pszFidColumn == nullptr && poFeature->GetFID() == 0)
345 : {
346 : // Miht be the case for views with joins.
347 0 : poFeature->SetFID(m_iNextShapeId);
348 : }
349 : }
350 : else
351 1650 : poFeature->SetFID(m_iNextShapeId);
352 :
353 13709 : m_iNextShapeId++;
354 :
355 13709 : m_nFeaturesRead++;
356 :
357 : /* -------------------------------------------------------------------- */
358 : /* Process Geometry if we have a column. */
359 : /* -------------------------------------------------------------------- */
360 13709 : if (m_iGeomCol >= 0)
361 : {
362 : OGRGeomFieldDefn *poGeomFieldDefn =
363 12009 : m_poFeatureDefn->GetGeomFieldDefn(0);
364 23953 : if (sqlite3_column_type(hStmt, m_iGeomCol) != SQLITE_NULL &&
365 11944 : !poGeomFieldDefn->IsIgnored())
366 : {
367 11934 : const OGRSpatialReference *poSrs = poGeomFieldDefn->GetSpatialRef();
368 11934 : int iGpkgSize = sqlite3_column_bytes(hStmt, m_iGeomCol);
369 : // coverity[tainted_data_return]
370 : const GByte *pabyGpkg = static_cast<const GByte *>(
371 11934 : sqlite3_column_blob(hStmt, m_iGeomCol));
372 : OGRGeometry *poGeom =
373 11934 : GPkgGeometryToOGR(pabyGpkg, iGpkgSize, nullptr);
374 11934 : if (poGeom == nullptr)
375 : {
376 : // Try also spatialite geometry blobs
377 3 : if (OGRSQLiteImportSpatiaLiteGeometry(pabyGpkg, iGpkgSize,
378 3 : &poGeom) != OGRERR_NONE)
379 : {
380 0 : CPLError(CE_Failure, CPLE_AppDefined,
381 : "Unable to read geometry");
382 : }
383 : }
384 11934 : if (poGeom)
385 : {
386 11934 : if (m_bUndoDiscardCoordLSBOnReading)
387 : {
388 3 : poGeom->roundCoordinates(
389 : poGeomFieldDefn->GetCoordinatePrecision());
390 : }
391 11934 : poGeom->assignSpatialReference(poSrs);
392 : }
393 :
394 11934 : poFeature->SetGeometryDirectly(poGeom);
395 : }
396 : }
397 :
398 : /* -------------------------------------------------------------------- */
399 : /* set the fields. */
400 : /* -------------------------------------------------------------------- */
401 13709 : const int nFieldCount = m_poFeatureDefn->GetFieldCount();
402 203873 : for (int iField = 0; iField < nFieldCount; iField++)
403 : {
404 : const OGRFieldDefn *poFieldDefn =
405 190164 : m_poFeatureDefn->GetFieldDefnUnsafe(iField);
406 190164 : if (poFieldDefn->IsIgnored())
407 31 : continue;
408 :
409 190133 : const int iRawField = m_anFieldOrdinals[iField];
410 :
411 190133 : const int nSqlite3ColType = sqlite3_column_type(hStmt, iRawField);
412 190133 : if (nSqlite3ColType == SQLITE_NULL)
413 : {
414 700 : poFeature->SetFieldNull(iField);
415 700 : continue;
416 : }
417 :
418 189433 : switch (poFieldDefn->GetType())
419 : {
420 1808 : case OFTInteger:
421 1808 : poFeature->SetFieldSameTypeUnsafe(
422 : iField, sqlite3_column_int(hStmt, iRawField));
423 1808 : break;
424 :
425 662 : case OFTInteger64:
426 662 : poFeature->SetFieldSameTypeUnsafe(
427 : iField, sqlite3_column_int64(hStmt, iRawField));
428 662 : break;
429 :
430 1118 : case OFTReal:
431 1118 : poFeature->SetFieldSameTypeUnsafe(
432 : iField, sqlite3_column_double(hStmt, iRawField));
433 1118 : break;
434 :
435 2601 : case OFTBinary:
436 : {
437 2601 : const int nBytes = sqlite3_column_bytes(hStmt, iRawField);
438 : // coverity[tainted_data_return]
439 2601 : const void *pabyData = sqlite3_column_blob(hStmt, iRawField);
440 2601 : if (pabyData != nullptr || nBytes == 0)
441 : {
442 2601 : poFeature->SetField(iField, nBytes, pabyData);
443 : }
444 : else
445 : {
446 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
447 0 : sqlite3_errmsg(m_poDS->GetDB()));
448 : }
449 2601 : break;
450 : }
451 :
452 412 : case OFTDate:
453 : {
454 412 : auto psField = poFeature->GetRawFieldRef(iField);
455 412 : CPL_IGNORE_RET_VAL(
456 412 : ParseDateField(hStmt, iRawField, nSqlite3ColType, psField,
457 : poFieldDefn, poFeature->GetFID()));
458 412 : break;
459 : }
460 :
461 399 : case OFTDateTime:
462 : {
463 399 : auto psField = poFeature->GetRawFieldRef(iField);
464 399 : CPL_IGNORE_RET_VAL(ParseDateTimeField(
465 : hStmt, iRawField, nSqlite3ColType, psField, poFieldDefn,
466 : poFeature->GetFID()));
467 399 : break;
468 : }
469 :
470 182433 : case OFTString:
471 : {
472 : const char *pszTxt = reinterpret_cast<const char *>(
473 182433 : sqlite3_column_text(hStmt, iRawField));
474 182433 : if (pszTxt)
475 : {
476 182433 : char *pszTxtDup = VSI_STRDUP_VERBOSE(pszTxt);
477 182433 : if (pszTxtDup)
478 : {
479 182433 : poFeature->SetFieldSameTypeUnsafe(iField, pszTxtDup);
480 : }
481 : }
482 : else
483 : {
484 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
485 0 : sqlite3_errmsg(m_poDS->GetDB()));
486 : }
487 182433 : break;
488 : }
489 :
490 0 : default:
491 0 : break;
492 : }
493 : }
494 :
495 13709 : return poFeature;
496 : }
497 :
498 : /************************************************************************/
499 : /* GetArrowStream() */
500 : /************************************************************************/
501 :
502 99 : bool OGRGeoPackageLayer::GetArrowStream(struct ArrowArrayStream *out_stream,
503 : CSLConstList papszOptions)
504 : {
505 198 : CPLStringList aosOptions;
506 99 : aosOptions.Assign(CSLDuplicate(papszOptions), true);
507 : // GeoPackage are assumed to be in UTC. Even if another timezone is used,
508 : // we'll do the conversion to UTC
509 99 : if (aosOptions.FetchNameValue("TIMEZONE") == nullptr)
510 : {
511 98 : aosOptions.SetNameValue("TIMEZONE", "UTC");
512 : }
513 198 : return OGRLayer::GetArrowStream(out_stream, aosOptions.List());
514 : }
515 :
516 : /************************************************************************/
517 : /* GetNextArrowArray() */
518 : /************************************************************************/
519 :
520 105 : int OGRGeoPackageLayer::GetNextArrowArray(struct ArrowArrayStream *stream,
521 : struct ArrowArray *out_array)
522 : {
523 105 : if (CPLTestBool(CPLGetConfigOption("OGR_GPKG_STREAM_BASE_IMPL", "NO")))
524 : {
525 6 : return OGRLayer::GetNextArrowArray(stream, out_array);
526 : }
527 :
528 99 : int errorErrno = EIO;
529 99 : memset(out_array, 0, sizeof(*out_array));
530 :
531 99 : if (m_bEOF)
532 16 : return 0;
533 :
534 83 : if (m_poQueryStatement == nullptr)
535 : {
536 15 : GetLayerDefn();
537 15 : ResetStatement();
538 15 : if (m_poQueryStatement == nullptr)
539 0 : return 0;
540 : }
541 83 : sqlite3_stmt *hStmt = m_poQueryStatement;
542 :
543 83 : OGRArrowArrayHelper sHelper(m_poDS, m_poFeatureDefn,
544 166 : m_aosArrowArrayStreamOptions, out_array);
545 83 : if (out_array->release == nullptr)
546 : {
547 0 : return ENOMEM;
548 : }
549 :
550 : struct tm brokenDown;
551 83 : memset(&brokenDown, 0, sizeof(brokenDown));
552 :
553 83 : const uint32_t nMemLimit = OGRArrowArrayHelper::GetMemLimit();
554 83 : int iFeat = 0;
555 289 : while (iFeat < sHelper.m_nMaxBatchSize)
556 : {
557 : /* --------------------------------------------------------------------
558 : */
559 : /* Fetch a record (unless otherwise instructed) */
560 : /* --------------------------------------------------------------------
561 : */
562 287 : if (m_bDoStep)
563 : {
564 222 : int rc = sqlite3_step(hStmt);
565 222 : if (rc != SQLITE_ROW)
566 : {
567 19 : if (rc != SQLITE_DONE)
568 : {
569 0 : sqlite3_reset(hStmt);
570 0 : CPLError(CE_Failure, CPLE_AppDefined,
571 : "In GetNextArrowArray(): sqlite3_step() : %s",
572 0 : sqlite3_errmsg(m_poDS->GetDB()));
573 : }
574 :
575 19 : ClearStatement();
576 19 : m_bEOF = true;
577 :
578 19 : break;
579 : }
580 : }
581 : else
582 : {
583 65 : m_bDoStep = true;
584 : }
585 :
586 268 : m_iNextShapeId++;
587 :
588 268 : m_nFeaturesRead++;
589 :
590 : GIntBig nFID;
591 268 : if (m_iFIDCol >= 0)
592 : {
593 268 : nFID = sqlite3_column_int64(hStmt, m_iFIDCol);
594 268 : if (m_pszFidColumn == nullptr && nFID == 0)
595 : {
596 : // Might be the case for views with joins.
597 0 : nFID = m_iNextShapeId;
598 : }
599 : }
600 : else
601 0 : nFID = m_iNextShapeId;
602 :
603 268 : if (sHelper.m_panFIDValues)
604 : {
605 268 : sHelper.m_panFIDValues[iFeat] = nFID;
606 : }
607 :
608 : /* --------------------------------------------------------------------
609 : */
610 : /* Process Geometry if we have a column. */
611 : /* --------------------------------------------------------------------
612 : */
613 268 : if (m_iGeomCol >= 0 && sHelper.m_mapOGRGeomFieldToArrowField[0] >= 0)
614 : {
615 268 : const int iArrowField = sHelper.m_mapOGRGeomFieldToArrowField[0];
616 268 : auto psArray = out_array->children[iArrowField];
617 :
618 268 : size_t nWKBSize = 0;
619 268 : if (sqlite3_column_type(hStmt, m_iGeomCol) != SQLITE_NULL)
620 : {
621 0 : std::unique_ptr<OGRGeometry> poGeom;
622 129 : const GByte *pabyWkb = nullptr;
623 129 : const int iGpkgSize = sqlite3_column_bytes(hStmt, m_iGeomCol);
624 : // coverity[tainted_data_return]
625 : const GByte *pabyGpkg = static_cast<const GByte *>(
626 129 : sqlite3_column_blob(hStmt, m_iGeomCol));
627 129 : if (m_poFilterGeom == nullptr && iGpkgSize >= 8 && pabyGpkg &&
628 107 : pabyGpkg[0] == 'G' && pabyGpkg[1] == 'P' &&
629 107 : !m_bUndoDiscardCoordLSBOnReading)
630 : {
631 : GPkgHeader oHeader;
632 :
633 : /* Read header */
634 : OGRErr err =
635 107 : GPkgHeaderFromWKB(pabyGpkg, iGpkgSize, &oHeader);
636 107 : if (err == OGRERR_NONE)
637 : {
638 : /* WKB pointer */
639 107 : pabyWkb = pabyGpkg + oHeader.nHeaderLen;
640 107 : nWKBSize = iGpkgSize - oHeader.nHeaderLen;
641 107 : }
642 : }
643 : else
644 : {
645 22 : poGeom.reset(
646 : GPkgGeometryToOGR(pabyGpkg, iGpkgSize, nullptr));
647 22 : if (poGeom == nullptr)
648 : {
649 : // Try also spatialite geometry blobs
650 0 : OGRGeometry *poGeomPtr = nullptr;
651 0 : if (OGRSQLiteImportSpatiaLiteGeometry(
652 0 : pabyGpkg, iGpkgSize, &poGeomPtr) != OGRERR_NONE)
653 : {
654 0 : CPLError(CE_Failure, CPLE_AppDefined,
655 : "Unable to read geometry");
656 : }
657 0 : poGeom.reset(poGeomPtr);
658 : }
659 22 : else if (m_bUndoDiscardCoordLSBOnReading)
660 : {
661 0 : poGeom->roundCoordinates(
662 0 : m_poFeatureDefn->GetGeomFieldDefn(0)
663 : ->GetCoordinatePrecision());
664 : }
665 22 : if (poGeom != nullptr)
666 : {
667 22 : nWKBSize = poGeom->WkbSize();
668 : }
669 44 : if (m_poFilterGeom != nullptr &&
670 22 : !FilterGeometry(poGeom.get()))
671 : {
672 2 : continue;
673 : }
674 : }
675 :
676 127 : if (nWKBSize != 0)
677 : {
678 127 : if (iFeat > 0)
679 : {
680 91 : auto panOffsets = static_cast<int32_t *>(
681 91 : const_cast<void *>(psArray->buffers[1]));
682 91 : const uint32_t nCurLength =
683 91 : static_cast<uint32_t>(panOffsets[iFeat]);
684 91 : if (nWKBSize <= nMemLimit &&
685 91 : nWKBSize > nMemLimit - nCurLength)
686 : {
687 24 : m_bDoStep = false;
688 24 : break;
689 : }
690 : }
691 :
692 103 : GByte *outPtr = sHelper.GetPtrForStringOrBinary(
693 : iArrowField, iFeat, nWKBSize);
694 103 : if (outPtr == nullptr)
695 : {
696 0 : errorErrno = ENOMEM;
697 0 : goto error;
698 : }
699 103 : if (poGeom)
700 : {
701 20 : poGeom->exportToWkb(wkbNDR, outPtr, wkbVariantIso);
702 : }
703 : else
704 : {
705 83 : memcpy(outPtr, pabyWkb, nWKBSize);
706 : }
707 : }
708 : else
709 : {
710 0 : sHelper.SetEmptyStringOrBinary(psArray, iFeat);
711 : }
712 : }
713 :
714 242 : if (nWKBSize == 0)
715 : {
716 139 : if (!sHelper.SetNull(iArrowField, iFeat))
717 : {
718 0 : errorErrno = ENOMEM;
719 0 : goto error;
720 : }
721 : }
722 : }
723 :
724 1735 : for (int iField = 0; iField < sHelper.m_nFieldCount; iField++)
725 : {
726 1531 : const int iArrowField = sHelper.m_mapOGRFieldToArrowField[iField];
727 1531 : if (iArrowField < 0)
728 0 : continue;
729 : const OGRFieldDefn *poFieldDefn =
730 1531 : m_poFeatureDefn->GetFieldDefnUnsafe(iField);
731 :
732 1531 : auto psArray = out_array->children[iArrowField];
733 1531 : const int iRawField = m_anFieldOrdinals[iField];
734 :
735 1531 : const int nSqlite3ColType = sqlite3_column_type(hStmt, iRawField);
736 1531 : if (nSqlite3ColType == SQLITE_NULL)
737 : {
738 259 : if (!sHelper.SetNull(iArrowField, iFeat))
739 : {
740 0 : errorErrno = ENOMEM;
741 0 : goto error;
742 : }
743 259 : continue;
744 : }
745 :
746 1272 : switch (poFieldDefn->GetType())
747 : {
748 876 : case OFTInteger:
749 : {
750 876 : const int nVal = sqlite3_column_int(hStmt, iRawField);
751 876 : if (poFieldDefn->GetSubType() == OFSTBoolean)
752 : {
753 41 : if (nVal != 0)
754 : {
755 40 : sHelper.SetBoolOn(psArray, iFeat);
756 : }
757 : }
758 835 : else if (poFieldDefn->GetSubType() == OFSTInt16)
759 : {
760 41 : sHelper.SetInt16(psArray, iFeat,
761 : static_cast<int16_t>(nVal));
762 : }
763 : else
764 : {
765 794 : sHelper.SetInt32(psArray, iFeat, nVal);
766 : }
767 876 : break;
768 : }
769 :
770 40 : case OFTInteger64:
771 : {
772 40 : sHelper.SetInt64(psArray, iFeat,
773 40 : sqlite3_column_int64(hStmt, iRawField));
774 40 : break;
775 : }
776 :
777 80 : case OFTReal:
778 : {
779 : const double dfVal =
780 80 : sqlite3_column_double(hStmt, iRawField);
781 80 : if (poFieldDefn->GetSubType() == OFSTFloat32)
782 : {
783 40 : sHelper.SetFloat(psArray, iFeat,
784 : static_cast<float>(dfVal));
785 : }
786 : else
787 : {
788 40 : sHelper.SetDouble(psArray, iFeat, dfVal);
789 : }
790 80 : break;
791 : }
792 :
793 98 : case OFTBinary:
794 : {
795 : const uint32_t nBytes = static_cast<uint32_t>(
796 98 : sqlite3_column_bytes(hStmt, iRawField));
797 : // coverity[tainted_data_return]
798 : const void *pabyData =
799 98 : sqlite3_column_blob(hStmt, iRawField);
800 98 : if (pabyData != nullptr || nBytes == 0)
801 : {
802 98 : if (iFeat > 0)
803 : {
804 74 : auto panOffsets = static_cast<int32_t *>(
805 74 : const_cast<void *>(psArray->buffers[1]));
806 74 : const uint32_t nCurLength =
807 74 : static_cast<uint32_t>(panOffsets[iFeat]);
808 74 : if (nBytes <= nMemLimit &&
809 74 : nBytes > nMemLimit - nCurLength)
810 : {
811 19 : m_bDoStep = false;
812 19 : m_iNextShapeId--;
813 19 : m_nFeaturesRead--;
814 19 : goto after_loop;
815 : }
816 : }
817 :
818 79 : GByte *outPtr = sHelper.GetPtrForStringOrBinary(
819 : iArrowField, iFeat, nBytes);
820 79 : if (outPtr == nullptr)
821 : {
822 0 : errorErrno = ENOMEM;
823 0 : goto error;
824 : }
825 79 : if (nBytes)
826 79 : memcpy(outPtr, pabyData, nBytes);
827 : }
828 : else
829 : {
830 0 : sHelper.SetEmptyStringOrBinary(psArray, iFeat);
831 : }
832 79 : break;
833 : }
834 :
835 40 : case OFTDate:
836 : {
837 : OGRField ogrField;
838 40 : if (ParseDateField(hStmt, iRawField, nSqlite3ColType,
839 : &ogrField, poFieldDefn, nFID))
840 : {
841 40 : sHelper.SetDate(psArray, iFeat, brokenDown, ogrField);
842 : }
843 40 : break;
844 : }
845 :
846 40 : case OFTDateTime:
847 : {
848 : OGRField ogrField;
849 40 : if (ParseDateTimeField(hStmt, iRawField, nSqlite3ColType,
850 : &ogrField, poFieldDefn, nFID))
851 : {
852 40 : sHelper.SetDateTime(psArray, iFeat, brokenDown,
853 40 : sHelper.m_anTZFlags[iField],
854 : ogrField);
855 : }
856 40 : break;
857 : }
858 :
859 98 : case OFTString:
860 : {
861 : const auto pszTxt = reinterpret_cast<const char *>(
862 98 : sqlite3_column_text(hStmt, iRawField));
863 98 : if (pszTxt != nullptr)
864 : {
865 98 : const size_t nBytes = strlen(pszTxt);
866 98 : if (iFeat > 0)
867 : {
868 74 : auto panOffsets = static_cast<int32_t *>(
869 74 : const_cast<void *>(psArray->buffers[1]));
870 74 : const uint32_t nCurLength =
871 74 : static_cast<uint32_t>(panOffsets[iFeat]);
872 74 : if (nBytes <= nMemLimit &&
873 74 : nBytes > nMemLimit - nCurLength)
874 : {
875 19 : m_bDoStep = false;
876 19 : m_iNextShapeId--;
877 19 : m_nFeaturesRead--;
878 19 : goto after_loop;
879 : }
880 : }
881 :
882 79 : GByte *outPtr = sHelper.GetPtrForStringOrBinary(
883 : iArrowField, iFeat, nBytes);
884 79 : if (outPtr == nullptr)
885 : {
886 0 : errorErrno = ENOMEM;
887 0 : goto error;
888 : }
889 79 : if (nBytes)
890 79 : memcpy(outPtr, pszTxt, nBytes);
891 : }
892 : else
893 : {
894 0 : sHelper.SetEmptyStringOrBinary(psArray, iFeat);
895 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
896 0 : sqlite3_errmsg(m_poDS->GetDB()));
897 : }
898 79 : break;
899 : }
900 :
901 0 : default:
902 0 : break;
903 : }
904 : }
905 :
906 204 : ++iFeat;
907 : }
908 2 : after_loop:
909 83 : sHelper.Shrink(iFeat);
910 83 : if (iFeat == 0)
911 4 : sHelper.ClearArray();
912 :
913 83 : return 0;
914 :
915 0 : error:
916 0 : sHelper.ClearArray();
917 0 : return errorErrno;
918 : }
919 :
920 : /************************************************************************/
921 : /* GetFIDColumn() */
922 : /************************************************************************/
923 :
924 2394 : const char *OGRGeoPackageLayer::GetFIDColumn()
925 : {
926 2394 : if (!m_pszFidColumn)
927 1 : return "";
928 : else
929 2393 : return m_pszFidColumn;
930 : }
931 :
932 : /************************************************************************/
933 : /* TestCapability() */
934 : /************************************************************************/
935 :
936 61 : int OGRGeoPackageLayer::TestCapability(const char *pszCap)
937 : {
938 61 : if (EQUAL(pszCap, OLCIgnoreFields))
939 5 : return TRUE;
940 56 : else if (EQUAL(pszCap, OLCStringsAsUTF8))
941 4 : return TRUE;
942 52 : else if (EQUAL(pszCap, OLCFastGetArrowStream))
943 27 : return TRUE;
944 25 : else if (EQUAL(pszCap, OLCZGeometries))
945 3 : return TRUE;
946 : else
947 22 : return FALSE;
948 : }
949 :
950 : /************************************************************************/
951 : /* BuildFeatureDefn() */
952 : /* */
953 : /* Build feature definition from a set of column definitions */
954 : /* set on a statement. Sift out geometry and FID fields. */
955 : /************************************************************************/
956 :
957 758 : void OGRGeoPackageLayer::BuildFeatureDefn(const char *pszLayerName,
958 : sqlite3_stmt *hStmt)
959 :
960 : {
961 758 : m_poFeatureDefn = new OGRSQLiteFeatureDefn(pszLayerName);
962 758 : SetDescription(m_poFeatureDefn->GetName());
963 758 : m_poFeatureDefn->SetGeomType(wkbNone);
964 758 : m_poFeatureDefn->Reference();
965 :
966 758 : const int nRawColumns = sqlite3_column_count(hStmt);
967 :
968 758 : m_anFieldOrdinals.resize(nRawColumns);
969 :
970 : const bool bPromoteToInteger64 =
971 758 : CPLTestBool(CPLGetConfigOption("OGR_PROMOTE_TO_INTEGER64", "FALSE"));
972 :
973 : #ifdef SQLITE_HAS_COLUMN_METADATA
974 : // Check that there are not several FID fields referenced.
975 : // This is not a sufficient condition to ensure that we can get a true FID,
976 : // but when this occurs, we are (almost) sure that this cannot be a FID.
977 758 : int nFIDCandidates = 0;
978 2990 : for (int iCol = 0; iCol < nRawColumns; iCol++)
979 : {
980 2232 : const char *pszTableName = sqlite3_column_table_name(hStmt, iCol);
981 2232 : const char *pszOriginName = sqlite3_column_origin_name(hStmt, iCol);
982 2232 : if (pszTableName != nullptr && pszOriginName != nullptr)
983 : {
984 1645 : OGRLayer *poLayer = m_poDS->GetLayerByName(pszTableName);
985 1645 : if (poLayer != nullptr)
986 : {
987 241 : if (EQUAL(pszOriginName, poLayer->GetFIDColumn()))
988 : {
989 45 : nFIDCandidates++;
990 : }
991 : }
992 : }
993 : }
994 : #endif
995 :
996 758 : bool bGeometryColumnGuessed = false;
997 2990 : for (int iCol = 0; iCol < nRawColumns; iCol++)
998 : {
999 2232 : OGRFieldDefn oField(SQLUnescape(sqlite3_column_name(hStmt, iCol)),
1000 2232 : OFTString);
1001 :
1002 : // In some cases, particularly when there is a real name for
1003 : // the primary key/_rowid_ column we will end up getting the
1004 : // primary key column appearing twice. Ignore any repeated names.
1005 2232 : if (m_poFeatureDefn->GetFieldIndex(oField.GetNameRef()) != -1)
1006 3 : continue;
1007 :
1008 2424 : if (m_pszFidColumn != nullptr &&
1009 195 : EQUAL(m_pszFidColumn, oField.GetNameRef()))
1010 3 : continue;
1011 :
1012 : // The rowid is for internal use, not a real column.
1013 2226 : if (EQUAL(oField.GetNameRef(), "_rowid_"))
1014 0 : continue;
1015 :
1016 : // this will avoid the old geom field to appear when running something
1017 : // like "select st_buffer(geom,5) as geom, * from my_layer"
1018 2382 : if (m_poFeatureDefn->GetGeomFieldCount() &&
1019 156 : EQUAL(oField.GetNameRef(),
1020 : m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef()))
1021 : {
1022 3 : continue;
1023 : }
1024 :
1025 : #ifdef SQLITE_HAS_COLUMN_METADATA
1026 2223 : const char *pszTableName = sqlite3_column_table_name(hStmt, iCol);
1027 2223 : const char *pszOriginName = sqlite3_column_origin_name(hStmt, iCol);
1028 2223 : if (pszTableName != nullptr && pszOriginName != nullptr)
1029 : {
1030 1639 : OGRLayer *poLayer = m_poDS->GetLayerByName(pszTableName);
1031 1639 : if (poLayer != nullptr)
1032 : {
1033 235 : if (EQUAL(pszOriginName, poLayer->GetGeometryColumn()))
1034 : {
1035 80 : if (bGeometryColumnGuessed ||
1036 40 : m_poFeatureDefn->GetGeomFieldCount() == 0)
1037 : {
1038 39 : if (bGeometryColumnGuessed)
1039 0 : m_poFeatureDefn->DeleteGeomFieldDefn(0);
1040 : OGRGeomFieldDefn oGeomField(
1041 39 : poLayer->GetLayerDefn()->GetGeomFieldDefn(0));
1042 39 : oGeomField.SetName(oField.GetNameRef());
1043 39 : m_poFeatureDefn->AddGeomFieldDefn(&oGeomField);
1044 39 : m_iGeomCol = iCol;
1045 : }
1046 40 : continue;
1047 : }
1048 195 : else if (EQUAL(pszOriginName, poLayer->GetFIDColumn()) &&
1049 195 : m_pszFidColumn == nullptr && nFIDCandidates == 1)
1050 : {
1051 40 : m_pszFidColumn = CPLStrdup(oField.GetNameRef());
1052 40 : m_iFIDCol = iCol;
1053 40 : continue;
1054 : }
1055 : int nSrcIdx =
1056 155 : poLayer->GetLayerDefn()->GetFieldIndex(oField.GetNameRef());
1057 155 : if (nSrcIdx >= 0)
1058 : {
1059 : OGRFieldDefn *poSrcField =
1060 152 : poLayer->GetLayerDefn()->GetFieldDefn(nSrcIdx);
1061 152 : oField.SetType(poSrcField->GetType());
1062 152 : oField.SetSubType(poSrcField->GetSubType());
1063 152 : oField.SetWidth(poSrcField->GetWidth());
1064 152 : oField.SetPrecision(poSrcField->GetPrecision());
1065 152 : oField.SetDomainName(poSrcField->GetDomainName());
1066 152 : m_poFeatureDefn->AddFieldDefn(&oField);
1067 152 : m_anFieldOrdinals[m_poFeatureDefn->GetFieldCount() - 1] =
1068 : iCol;
1069 152 : continue;
1070 : }
1071 : }
1072 : }
1073 : #endif
1074 :
1075 1991 : const int nColType = sqlite3_column_type(hStmt, iCol);
1076 1991 : if (m_poFeatureDefn->GetGeomFieldCount() == 0 &&
1077 2445 : m_pszFidColumn == nullptr && nColType == SQLITE_INTEGER &&
1078 454 : EQUAL(oField.GetNameRef(), "FID"))
1079 : {
1080 2 : m_pszFidColumn = CPLStrdup(oField.GetNameRef());
1081 2 : m_iFIDCol = iCol;
1082 2 : continue;
1083 : }
1084 :
1085 : // Heuristics to help for https://github.com/OSGeo/gdal/issues/8587
1086 1989 : if (nColType == SQLITE_NULL && m_iGeomCol < 0
1087 : #ifdef SQLITE_HAS_COLUMN_METADATA
1088 570 : && !pszTableName && !pszOriginName
1089 : #endif
1090 : )
1091 : {
1092 224 : bool bIsLikelyGeomColName = EQUAL(oField.GetNameRef(), "geom") ||
1093 111 : EQUAL(oField.GetNameRef(), "geometry");
1094 113 : bool bIsGeomFunction = false;
1095 113 : if (!bIsLikelyGeomColName)
1096 111 : bIsGeomFunction = OGRSQLiteIsSpatialFunctionReturningGeometry(
1097 : oField.GetNameRef());
1098 113 : if (bIsLikelyGeomColName || bIsGeomFunction)
1099 : {
1100 10 : bGeometryColumnGuessed = bIsLikelyGeomColName;
1101 20 : OGRGeomFieldDefn oGeomField(oField.GetNameRef(), wkbUnknown);
1102 10 : m_poFeatureDefn->AddGeomFieldDefn(&oGeomField);
1103 10 : m_iGeomCol = iCol;
1104 10 : continue;
1105 : }
1106 : }
1107 :
1108 1979 : const char *pszDeclType = sqlite3_column_decltype(hStmt, iCol);
1109 :
1110 : // Recognize a geometry column from trying to build the geometry
1111 2038 : if (nColType == SQLITE_BLOB &&
1112 59 : m_poFeatureDefn->GetGeomFieldCount() == 0)
1113 : {
1114 58 : const int nBytes = sqlite3_column_bytes(hStmt, iCol);
1115 58 : if (nBytes >= 8)
1116 : {
1117 : // coverity[tainted_data_return]
1118 : const GByte *pabyGpkg = reinterpret_cast<const GByte *>(
1119 58 : sqlite3_column_blob(hStmt, iCol));
1120 : GPkgHeader oHeader;
1121 58 : OGRGeometry *poGeom = nullptr;
1122 58 : int nSRID = 0;
1123 :
1124 58 : if (GPkgHeaderFromWKB(pabyGpkg, nBytes, &oHeader) ==
1125 : OGRERR_NONE)
1126 : {
1127 7 : poGeom = GPkgGeometryToOGR(pabyGpkg, nBytes, nullptr);
1128 7 : nSRID = oHeader.iSrsId;
1129 : }
1130 : else
1131 : {
1132 : // Try also spatialite geometry blobs
1133 51 : if (OGRSQLiteImportSpatiaLiteGeometry(
1134 51 : pabyGpkg, nBytes, &poGeom, &nSRID) != OGRERR_NONE)
1135 : {
1136 49 : delete poGeom;
1137 49 : poGeom = nullptr;
1138 : }
1139 : }
1140 :
1141 58 : if (poGeom)
1142 : {
1143 : OGRGeomFieldDefn oGeomField(oField.GetNameRef(),
1144 18 : wkbUnknown);
1145 :
1146 : /* Read the SRS */
1147 18 : auto poSRS = m_poDS->GetSpatialRef(nSRID, true);
1148 9 : if (poSRS)
1149 : {
1150 7 : oGeomField.SetSpatialRef(poSRS.get());
1151 : }
1152 :
1153 9 : OGRwkbGeometryType eGeomType = poGeom->getGeometryType();
1154 9 : if (pszDeclType != nullptr)
1155 : {
1156 : OGRwkbGeometryType eDeclaredGeomType =
1157 0 : GPkgGeometryTypeToWKB(pszDeclType, false, false);
1158 0 : if (eDeclaredGeomType != wkbUnknown)
1159 : {
1160 0 : eGeomType = OGR_GT_SetModifier(
1161 : eDeclaredGeomType, OGR_GT_HasZ(eGeomType),
1162 : OGR_GT_HasM(eGeomType));
1163 : }
1164 : }
1165 9 : oGeomField.SetType(eGeomType);
1166 :
1167 9 : delete poGeom;
1168 9 : poGeom = nullptr;
1169 :
1170 9 : m_poFeatureDefn->AddGeomFieldDefn(&oGeomField);
1171 9 : m_iGeomCol = iCol;
1172 9 : continue;
1173 : }
1174 : }
1175 : }
1176 :
1177 1970 : switch (nColType)
1178 : {
1179 456 : case SQLITE_INTEGER:
1180 456 : if (bPromoteToInteger64)
1181 0 : oField.SetType(OFTInteger64);
1182 : else
1183 : {
1184 456 : GIntBig nVal = sqlite3_column_int64(hStmt, iCol);
1185 456 : if (CPL_INT64_FITS_ON_INT32(nVal))
1186 453 : oField.SetType(OFTInteger);
1187 : else
1188 3 : oField.SetType(OFTInteger64);
1189 : }
1190 456 : break;
1191 :
1192 382 : case SQLITE_FLOAT:
1193 382 : oField.SetType(OFTReal);
1194 382 : break;
1195 :
1196 50 : case SQLITE_BLOB:
1197 50 : oField.SetType(OFTBinary);
1198 50 : break;
1199 :
1200 1970 : default:
1201 : /* leave it as OFTString */;
1202 : }
1203 :
1204 1970 : if (pszDeclType != nullptr)
1205 : {
1206 : OGRFieldSubType eSubType;
1207 1358 : int nMaxWidth = 0;
1208 : const int nFieldType =
1209 1358 : GPkgFieldToOGR(pszDeclType, eSubType, nMaxWidth);
1210 1358 : if (nFieldType <= OFTMaxType)
1211 : {
1212 1358 : oField.SetType(static_cast<OGRFieldType>(nFieldType));
1213 1358 : oField.SetSubType(eSubType);
1214 1358 : oField.SetWidth(nMaxWidth);
1215 : }
1216 : }
1217 :
1218 1970 : m_poFeatureDefn->AddFieldDefn(&oField);
1219 1970 : m_anFieldOrdinals[m_poFeatureDefn->GetFieldCount() - 1] = iCol;
1220 : }
1221 758 : }
1222 :
1223 : /************************************************************************/
1224 : /* SetIgnoredFields() */
1225 : /************************************************************************/
1226 :
1227 16 : OGRErr OGRGeoPackageLayer::SetIgnoredFields(CSLConstList papszFields)
1228 : {
1229 16 : OGRErr eErr = OGRLayer::SetIgnoredFields(papszFields);
1230 16 : if (eErr == OGRERR_NONE)
1231 : {
1232 : // So that OGRGeoPackageTableLayer::BuildColumns() is called
1233 16 : ResetReading();
1234 : }
1235 16 : return eErr;
1236 : }
|