Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implements OGRPGDataSource class.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2000, Frank Warmerdam
9 : * Copyright (c) 2008-2014, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include <string.h>
15 : #include "ogr_pg.h"
16 : #include "cpl_conv.h"
17 : #include "cpl_string.h"
18 : #include "cpl_hash_set.h"
19 : #include <cctype>
20 : #include <set>
21 :
22 : #include "memdataset.h"
23 :
24 : #define PQexec this_is_an_error
25 :
26 : static void OGRPGNoticeProcessor(void *arg, const char *pszMessage);
27 :
28 : /************************************************************************/
29 : /* OGRPGDataSource() */
30 : /************************************************************************/
31 :
32 : OGRPGDataSource::OGRPGDataSource() = default;
33 :
34 : /************************************************************************/
35 : /* ~OGRPGDataSource() */
36 : /************************************************************************/
37 :
38 864 : OGRPGDataSource::~OGRPGDataSource()
39 :
40 : {
41 432 : OGRPGDataSource::FlushCache(true);
42 :
43 432 : CPLFree(pszForcedTables);
44 432 : CSLDestroy(papszSchemaList);
45 :
46 912 : for (int i = 0; i < nLayers; i++)
47 480 : delete papoLayers[i];
48 :
49 432 : CPLFree(papoLayers);
50 :
51 432 : if (hPGConn != nullptr)
52 : {
53 : // If there are prelude statements, don't mess with transactions.
54 429 : if (CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS") ==
55 : nullptr)
56 421 : FlushSoftTransaction();
57 :
58 : /* --------------------------------------------------------------------
59 : */
60 : /* Send closing statements */
61 : /* --------------------------------------------------------------------
62 : */
63 : const char *pszClosingStatements =
64 429 : CSLFetchNameValue(papszOpenOptions, "CLOSING_STATEMENTS");
65 429 : if (pszClosingStatements != nullptr)
66 : {
67 : PGresult *hResult =
68 6 : OGRPG_PQexec(hPGConn, pszClosingStatements, TRUE);
69 6 : OGRPGClearResult(hResult);
70 : }
71 :
72 : /* XXX - mloskot: After the connection is closed, valgrind still
73 : * reports 36 bytes definitely lost, somewhere in the libpq.
74 : */
75 429 : PQfinish(hPGConn);
76 429 : hPGConn = nullptr;
77 : }
78 864 : }
79 :
80 : /************************************************************************/
81 : /* FlushCache() */
82 : /************************************************************************/
83 :
84 1783 : OGRErr OGRPGDataSource::FlushCacheWithRet(bool /* bAtClosing */)
85 : {
86 1783 : OGRErr eErr = EndCopy();
87 1783 : if (eErr == OGRERR_NONE)
88 : {
89 3211 : for (int iLayer = 0; iLayer < nLayers; iLayer++)
90 : {
91 1429 : papoLayers[iLayer]->RunDeferredCreationIfNecessary();
92 : }
93 : }
94 1783 : return eErr;
95 : }
96 :
97 1727 : CPLErr OGRPGDataSource::FlushCache(bool bAtClosing)
98 : {
99 1727 : return FlushCacheWithRet(bAtClosing) == OGRERR_NONE ? CE_None : CE_Failure;
100 : }
101 :
102 : /************************************************************************/
103 : /* GetCurrentSchema() */
104 : /************************************************************************/
105 :
106 427 : CPLString OGRPGDataSource::GetCurrentSchema()
107 : {
108 : /* -------------------------------------------- */
109 : /* Get the current schema */
110 : /* -------------------------------------------- */
111 427 : PGresult *hResult = OGRPG_PQexec(hPGConn, "SELECT current_schema()");
112 427 : if (hResult && PQntuples(hResult) == 1 && !PQgetisnull(hResult, 0, 0))
113 : {
114 427 : osCurrentSchema = PQgetvalue(hResult, 0, 0);
115 : }
116 427 : OGRPGClearResult(hResult);
117 :
118 854 : return osCurrentSchema;
119 : }
120 :
121 : /************************************************************************/
122 : /* OGRPGDecodeVersionString() */
123 : /************************************************************************/
124 :
125 664 : void OGRPGDataSource::OGRPGDecodeVersionString(PGver *psVersion,
126 : const char *pszVer)
127 : {
128 : // Skip leading spaces
129 664 : while (*pszVer == ' ')
130 0 : pszVer++;
131 1328 : std::string osVer(pszVer);
132 : // And truncate at the first space
133 664 : const auto nPosSpace = osVer.find(' ');
134 664 : if (nPosSpace != std::string::npos)
135 664 : osVer.resize(nPosSpace);
136 :
137 664 : memset(psVersion, 0, sizeof(*psVersion));
138 1328 : const CPLStringList aosTokens(CSLTokenizeString2(osVer.c_str(), ".", 0));
139 664 : if (aosTokens.size() >= 1)
140 664 : psVersion->nMajor = atoi(aosTokens[0]);
141 664 : if (aosTokens.size() >= 2)
142 664 : psVersion->nMinor = atoi(aosTokens[1]);
143 664 : if (aosTokens.size() >= 3)
144 0 : psVersion->nRelease = atoi(aosTokens[2]);
145 664 : }
146 :
147 : /************************************************************************/
148 : /* One entry for each PG table */
149 : /************************************************************************/
150 :
151 : struct PGTableEntry
152 : {
153 : char *pszTableName = nullptr;
154 : char *pszSchemaName = nullptr;
155 : char *pszDescription = nullptr;
156 : int nGeomColumnCount = 0;
157 : PGGeomColumnDesc *pasGeomColumns = nullptr; /* list of geometry columns */
158 : int bDerivedInfoAdded =
159 : false; /* set to TRUE if it derives from another table */
160 : };
161 :
162 1448 : static unsigned long OGRPGHashTableEntry(const void *_psTableEntry)
163 : {
164 1448 : const PGTableEntry *psTableEntry =
165 : static_cast<const PGTableEntry *>(_psTableEntry);
166 4344 : return CPLHashSetHashStr(CPLString()
167 1448 : .Printf("%s.%s", psTableEntry->pszSchemaName,
168 1448 : psTableEntry->pszTableName)
169 4344 : .c_str());
170 : }
171 :
172 389 : static int OGRPGEqualTableEntry(const void *_psTableEntry1,
173 : const void *_psTableEntry2)
174 : {
175 389 : const PGTableEntry *psTableEntry1 =
176 : static_cast<const PGTableEntry *>(_psTableEntry1);
177 389 : const PGTableEntry *psTableEntry2 =
178 : static_cast<const PGTableEntry *>(_psTableEntry2);
179 389 : return strcmp(psTableEntry1->pszTableName, psTableEntry2->pszTableName) ==
180 772 : 0 &&
181 383 : strcmp(psTableEntry1->pszSchemaName, psTableEntry2->pszSchemaName) ==
182 389 : 0;
183 : }
184 :
185 158 : static void OGRPGTableEntryAddGeomColumn(
186 : PGTableEntry *psTableEntry, const char *pszName,
187 : const char *pszGeomType = nullptr, int GeometryTypeFlags = 0,
188 : int nSRID = UNDETERMINED_SRID, PostgisType ePostgisType = GEOM_TYPE_UNKNOWN,
189 : int bNullable = TRUE)
190 : {
191 316 : psTableEntry->pasGeomColumns = static_cast<PGGeomColumnDesc *>(CPLRealloc(
192 158 : psTableEntry->pasGeomColumns,
193 158 : sizeof(PGGeomColumnDesc) * (psTableEntry->nGeomColumnCount + 1)));
194 316 : psTableEntry->pasGeomColumns[psTableEntry->nGeomColumnCount].pszName =
195 158 : CPLStrdup(pszName);
196 158 : psTableEntry->pasGeomColumns[psTableEntry->nGeomColumnCount].pszGeomType =
197 158 : (pszGeomType) ? CPLStrdup(pszGeomType) : nullptr;
198 158 : psTableEntry->pasGeomColumns[psTableEntry->nGeomColumnCount]
199 158 : .GeometryTypeFlags = GeometryTypeFlags;
200 : /* With PostGIS 2.0, querying geometry_columns can return 0, not only when
201 : */
202 : /* the SRID is truly set to 0, but also when there's no constraint */
203 158 : psTableEntry->pasGeomColumns[psTableEntry->nGeomColumnCount].nSRID =
204 158 : nSRID > 0 ? nSRID : UNDETERMINED_SRID;
205 158 : psTableEntry->pasGeomColumns[psTableEntry->nGeomColumnCount].ePostgisType =
206 : ePostgisType;
207 158 : psTableEntry->pasGeomColumns[psTableEntry->nGeomColumnCount].bNullable =
208 : bNullable;
209 158 : psTableEntry->nGeomColumnCount++;
210 158 : }
211 :
212 730 : static void OGRPGFreeTableEntry(void *_psTableEntry)
213 : {
214 730 : PGTableEntry *psTableEntry = static_cast<PGTableEntry *>(_psTableEntry);
215 730 : CPLFree(psTableEntry->pszTableName);
216 730 : CPLFree(psTableEntry->pszSchemaName);
217 730 : CPLFree(psTableEntry->pszDescription);
218 888 : for (int i = 0; i < psTableEntry->nGeomColumnCount; i++)
219 : {
220 158 : CPLFree(psTableEntry->pasGeomColumns[i].pszName);
221 158 : CPLFree(psTableEntry->pasGeomColumns[i].pszGeomType);
222 : }
223 730 : CPLFree(psTableEntry->pasGeomColumns);
224 730 : CPLFree(psTableEntry);
225 730 : }
226 :
227 366 : static PGTableEntry *OGRPGFindTableEntry(CPLHashSet *hSetTables,
228 : const char *pszTableName,
229 : const char *pszSchemaName)
230 : {
231 366 : PGTableEntry sEntry;
232 366 : sEntry.pszTableName = const_cast<char *>(pszTableName);
233 366 : sEntry.pszSchemaName = const_cast<char *>(pszSchemaName);
234 732 : return static_cast<PGTableEntry *>(CPLHashSetLookup(hSetTables, &sEntry));
235 : }
236 :
237 352 : static PGTableEntry *OGRPGAddTableEntry(CPLHashSet *hSetTables,
238 : const char *pszTableName,
239 : const char *pszSchemaName,
240 : const char *pszDescription)
241 : {
242 : PGTableEntry *psEntry =
243 352 : static_cast<PGTableEntry *>(CPLCalloc(1, sizeof(PGTableEntry)));
244 352 : psEntry->pszTableName = CPLStrdup(pszTableName);
245 352 : psEntry->pszSchemaName = CPLStrdup(pszSchemaName);
246 352 : psEntry->pszDescription = CPLStrdup(pszDescription ? pszDescription : "");
247 :
248 352 : CPLHashSetInsert(hSetTables, psEntry);
249 :
250 352 : return psEntry;
251 : }
252 :
253 : /************************************************************************/
254 : /* Open() */
255 : /************************************************************************/
256 :
257 432 : int OGRPGDataSource::Open(const char *pszNewName, int bUpdate, int bTestOpen,
258 : char **papszOpenOptionsIn)
259 :
260 : {
261 432 : CPLAssert(nLayers == 0);
262 432 : papszOpenOptions = CSLDuplicate(papszOpenOptionsIn);
263 :
264 : const char *pszPreludeStatements =
265 432 : CSLFetchNameValue(papszOpenOptions, "PRELUDE_STATEMENTS");
266 432 : if (pszPreludeStatements)
267 : {
268 : // If the prelude statements starts with BEGIN, then don't emit one
269 : // in our code.
270 8 : if (STARTS_WITH_CI(pszPreludeStatements, "BEGIN"))
271 6 : nSoftTransactionLevel = 1;
272 : }
273 :
274 : /* -------------------------------------------------------------------- */
275 : /* Verify postgresql prefix. */
276 : /* -------------------------------------------------------------------- */
277 432 : if (STARTS_WITH_CI(pszNewName, "PGB:"))
278 : {
279 : #if defined(BINARY_CURSOR_ENABLED)
280 : bUseBinaryCursor = TRUE;
281 : CPLDebug("PG", "BINARY cursor is used for geometry fetching");
282 : #endif
283 : }
284 432 : else if (!STARTS_WITH_CI(pszNewName, "PG:") &&
285 2 : !STARTS_WITH(pszNewName, "postgresql://"))
286 : {
287 0 : if (!bTestOpen)
288 0 : CPLError(CE_Failure, CPLE_AppDefined,
289 : "%s does not conform to PostgreSQL naming convention,"
290 : " PG:* or postgresql://\n",
291 : pszNewName);
292 0 : return FALSE;
293 : }
294 :
295 427 : const auto QuoteAndEscapeConnectionParam = [](const char *pszParam)
296 : {
297 427 : CPLString osRet("\'");
298 11102 : for (int i = 0; pszParam[i]; ++i)
299 : {
300 10675 : if (pszParam[i] == '\'')
301 0 : osRet += "\\'";
302 10675 : else if (pszParam[i] == '\\')
303 0 : osRet += "\\\\";
304 : else
305 10675 : osRet += pszParam[i];
306 : }
307 427 : osRet += '\'';
308 427 : return osRet;
309 : };
310 :
311 864 : CPLString osConnectionName(pszNewName);
312 432 : if (osConnectionName.find("PG:postgresql://") == 0)
313 1 : osConnectionName = osConnectionName.substr(3);
314 432 : const bool bIsURI = osConnectionName.find("postgresql://") == 0;
315 :
316 432 : const char *const apszOpenOptions[] = {
317 : "service", "dbname", "port", "user", "password", "host",
318 : // Non-postgreSQL options
319 : "active_schema", "schemas", "tables"};
320 864 : std::string osSchemas;
321 864 : std::string osForcedTables;
322 4320 : for (const char *pszOpenOption : apszOpenOptions)
323 : {
324 3888 : const char *pszVal = CSLFetchNameValue(papszOpenOptions, pszOpenOption);
325 3888 : if (pszVal && strcmp(pszOpenOption, "active_schema") == 0)
326 : {
327 1 : osActiveSchema = pszVal;
328 : }
329 3887 : else if (pszVal && strcmp(pszOpenOption, "schemas") == 0)
330 : {
331 0 : osSchemas = pszVal;
332 : }
333 3887 : else if (pszVal && strcmp(pszOpenOption, "tables") == 0)
334 : {
335 6 : osForcedTables = pszVal;
336 : }
337 3881 : else if (pszVal)
338 : {
339 2 : if (bIsURI)
340 : {
341 : osConnectionName +=
342 2 : osConnectionName.find('?') == std::string::npos ? '?' : '&';
343 : }
344 : else
345 : {
346 0 : if (osConnectionName.back() != ':')
347 0 : osConnectionName += ' ';
348 : }
349 2 : osConnectionName += pszOpenOption;
350 2 : osConnectionName += "=";
351 2 : if (bIsURI)
352 : {
353 2 : char *pszTmp = CPLEscapeString(pszVal, -1, CPLES_URL);
354 2 : osConnectionName += pszTmp;
355 2 : CPLFree(pszTmp);
356 : }
357 : else
358 : {
359 0 : osConnectionName += QuoteAndEscapeConnectionParam(pszVal);
360 : }
361 : }
362 : }
363 :
364 : /* -------------------------------------------------------------------- */
365 : /* Set application name if not found in connection string */
366 : /* -------------------------------------------------------------------- */
367 :
368 862 : if (strstr(pszNewName, "application_name") == nullptr &&
369 430 : getenv("PGAPPNAME") == nullptr)
370 : {
371 430 : if (bIsURI)
372 : {
373 : osConnectionName +=
374 3 : osConnectionName.find('?') == std::string::npos ? '?' : '&';
375 : }
376 : else
377 : {
378 427 : if (osConnectionName.back() != ':')
379 427 : osConnectionName += ' ';
380 : }
381 430 : osConnectionName += "application_name=";
382 860 : std::string osVal("GDAL ");
383 430 : osVal += GDALVersionInfo("RELEASE_NAME");
384 430 : if (bIsURI)
385 : {
386 3 : char *pszTmp = CPLEscapeString(osVal.c_str(), -1, CPLES_URL);
387 3 : osConnectionName += pszTmp;
388 3 : CPLFree(pszTmp);
389 : }
390 : else
391 : {
392 427 : osConnectionName += QuoteAndEscapeConnectionParam(osVal.c_str());
393 : }
394 : }
395 :
396 : const auto ParseAndRemoveParam =
397 1281 : [](char *pszStr, const char *pszParamName, std::string &osValue)
398 : {
399 1281 : const int nParamNameLen = static_cast<int>(strlen(pszParamName));
400 1281 : bool bInSingleQuotedString = false;
401 158228 : for (int i = 0; pszStr[i]; i++)
402 : {
403 157379 : if (bInSingleQuotedString)
404 : {
405 22242 : if (pszStr[i] == '\\')
406 : {
407 12 : if (pszStr[i + 1] == '\\' || pszStr[i + 1] == '\'')
408 : {
409 12 : ++i;
410 : }
411 : }
412 22230 : else if (pszStr[i] == '\'')
413 : {
414 861 : bInSingleQuotedString = false;
415 : }
416 : }
417 135137 : else if (pszStr[i] == '\'')
418 : {
419 861 : bInSingleQuotedString = true;
420 : }
421 134276 : else if (EQUALN(pszStr + i, pszParamName, nParamNameLen) &&
422 432 : (pszStr[i + nParamNameLen] == '=' ||
423 3 : pszStr[i + nParamNameLen] == ' '))
424 : {
425 432 : const int iStart = i;
426 432 : i += nParamNameLen;
427 436 : while (pszStr[i] == ' ')
428 4 : ++i;
429 432 : if (pszStr[i] == '=')
430 : {
431 432 : ++i;
432 438 : while (pszStr[i] == ' ')
433 6 : ++i;
434 432 : if (pszStr[i] == '\'')
435 : {
436 2 : ++i;
437 106 : for (; pszStr[i]; i++)
438 : {
439 106 : if (pszStr[i] == '\\')
440 : {
441 0 : if (pszStr[i + 1] == '\\' ||
442 0 : pszStr[i + 1] == '\'')
443 : {
444 0 : osValue += pszStr[i + 1];
445 0 : ++i;
446 : }
447 : }
448 106 : else if (pszStr[i] == '\'')
449 : {
450 2 : ++i;
451 2 : break;
452 : }
453 : else
454 : {
455 104 : osValue += pszStr[i];
456 : }
457 : }
458 : }
459 : else
460 : {
461 12492 : for (; pszStr[i] && pszStr[i] != ' '; i++)
462 : {
463 12062 : osValue += pszStr[i];
464 : }
465 : }
466 :
467 : // Edit pszStr to remove the parameter and its value
468 432 : if (pszStr[i] == ' ')
469 : {
470 430 : memmove(pszStr + iStart, pszStr + i,
471 430 : strlen(pszStr + i) + 1);
472 : }
473 : else
474 : {
475 2 : pszStr[iStart] = 0;
476 : }
477 : }
478 432 : return true;
479 : }
480 : }
481 849 : return false;
482 : };
483 :
484 432 : char *pszConnectionName = CPLStrdup(osConnectionName);
485 432 : char *pszConnectionNameNoPrefix =
486 864 : pszConnectionName + (STARTS_WITH_CI(pszConnectionName, "PGB:") ? 4
487 432 : : STARTS_WITH_CI(pszConnectionName, "PG:") ? 3
488 : : 0);
489 :
490 : /* -------------------------------------------------------------------- */
491 : /* Determine if the connection string contains an optional */
492 : /* ACTIVE_SCHEMA portion. If so, parse it out. */
493 : /* -------------------------------------------------------------------- */
494 861 : if (osActiveSchema.empty() && !bIsURI &&
495 429 : !ParseAndRemoveParam(pszConnectionNameNoPrefix, "active_schema",
496 : osActiveSchema))
497 : {
498 425 : osActiveSchema = "public";
499 : }
500 :
501 : /* -------------------------------------------------------------------- */
502 : /* Determine if the connection string contains an optional */
503 : /* SCHEMAS portion. If so, parse it out. */
504 : /* -------------------------------------------------------------------- */
505 864 : if (!osSchemas.empty() ||
506 432 : (!bIsURI &&
507 429 : ParseAndRemoveParam(pszConnectionNameNoPrefix, "schemas", osSchemas)))
508 : {
509 422 : papszSchemaList = CSLTokenizeString2(osSchemas.c_str(), ",", 0);
510 :
511 : /* If there is only one schema specified, make it the active schema */
512 422 : if (CSLCount(papszSchemaList) == 1)
513 : {
514 420 : osActiveSchema = papszSchemaList[0];
515 : }
516 : }
517 :
518 : /* -------------------------------------------------------------------- */
519 : /* Determine if the connection string contains an optional */
520 : /* TABLES portion. If so, parse it out. The expected */
521 : /* connection string in this case will be, e.g.: */
522 : /* */
523 : /* 'PG:dbname=warmerda user=warmerda tables=s1.t1,[s2.t2,...] */
524 : /* - where sN is schema and tN is table name */
525 : /* We must also strip this information from the connection */
526 : /* string; PQconnectdb() does not like unknown directives */
527 : /* -------------------------------------------------------------------- */
528 858 : if (!osForcedTables.empty() ||
529 426 : (!bIsURI && ParseAndRemoveParam(pszConnectionNameNoPrefix, "tables",
530 : osForcedTables)))
531 : {
532 12 : pszForcedTables = CPLStrdup(osForcedTables.c_str());
533 : }
534 :
535 : /* -------------------------------------------------------------------- */
536 : /* Try to establish connection. */
537 : /* -------------------------------------------------------------------- */
538 432 : hPGConn = PQconnectdb(pszConnectionNameNoPrefix);
539 432 : CPLFree(pszConnectionName);
540 432 : pszConnectionName = nullptr;
541 :
542 432 : if (hPGConn == nullptr || PQstatus(hPGConn) == CONNECTION_BAD)
543 : {
544 3 : CPLError(CE_Failure, CPLE_AppDefined, "PQconnectdb failed.\n%s",
545 3 : PQerrorMessage(hPGConn));
546 :
547 3 : PQfinish(hPGConn);
548 3 : hPGConn = nullptr;
549 :
550 3 : return FALSE;
551 : }
552 :
553 429 : bDSUpdate = bUpdate;
554 :
555 : /* -------------------------------------------------------------------- */
556 : /* Send prelude statements */
557 : /* -------------------------------------------------------------------- */
558 429 : if (pszPreludeStatements != nullptr)
559 : {
560 8 : PGresult *hResult = OGRPG_PQexec(hPGConn, pszPreludeStatements, TRUE);
561 8 : if (!hResult || PQresultStatus(hResult) != PGRES_COMMAND_OK)
562 : {
563 2 : OGRPGClearResult(hResult);
564 2 : return FALSE;
565 : }
566 :
567 6 : OGRPGClearResult(hResult);
568 : }
569 :
570 : /* -------------------------------------------------------------------- */
571 : /* Set the encoding to UTF8 as the driver advertises UTF8 */
572 : /* unless PGCLIENTENCODING is defined */
573 : /* -------------------------------------------------------------------- */
574 427 : if (CPLGetConfigOption("PGCLIENTENCODING", nullptr) == nullptr)
575 : {
576 425 : const char *encoding = "UTF8";
577 425 : if (PQsetClientEncoding(hPGConn, encoding) == -1)
578 : {
579 0 : CPLError(CE_Warning, CPLE_AppDefined,
580 : "PQsetClientEncoding(%s) failed.\n%s", encoding,
581 0 : PQerrorMessage(hPGConn));
582 : }
583 : }
584 :
585 : {
586 427 : PGresult *hResult = OGRPG_PQexec(hPGConn, "SHOW client_encoding");
587 854 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK &&
588 427 : PQntuples(hResult) == 1)
589 : {
590 427 : const char *pszClientEncoding = PQgetvalue(hResult, 0, 0);
591 427 : if (pszClientEncoding)
592 : {
593 427 : CPLDebug("PG", "Client encoding: '%s'", pszClientEncoding);
594 427 : if (EQUAL(pszClientEncoding, "UTF8"))
595 : {
596 425 : m_bUTF8ClientEncoding = true;
597 : }
598 : }
599 : }
600 427 : OGRPGClearResult(hResult);
601 : }
602 :
603 : /* -------------------------------------------------------------------- */
604 : /* Install a notice processor. */
605 : /* -------------------------------------------------------------------- */
606 427 : PQsetNoticeProcessor(hPGConn, OGRPGNoticeProcessor, this);
607 :
608 : /* -------------------------------------------------------------------- */
609 : /* Detect PostGIS schema */
610 : /* -------------------------------------------------------------------- */
611 854 : CPLString osPostgisSchema;
612 : {
613 427 : PGresult *hResult = OGRPG_PQexec(
614 : hPGConn,
615 : "SELECT n.nspname FROM pg_proc p JOIN pg_namespace n "
616 427 : "ON n.oid = p.pronamespace WHERE proname = 'postgis_version'");
617 854 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK &&
618 427 : PQntuples(hResult) > 0)
619 : {
620 427 : const char *pszPostgisSchema = PQgetvalue(hResult, 0, 0);
621 :
622 427 : CPLDebug("PG", "PostGIS schema: '%s'", pszPostgisSchema);
623 :
624 427 : osPostgisSchema = pszPostgisSchema;
625 : }
626 427 : OGRPGClearResult(hResult);
627 : }
628 :
629 : /* -------------------------------------------------------------------- */
630 : /* Get search_path */
631 : /* -------------------------------------------------------------------- */
632 854 : std::string osSearchPath = "public";
633 : {
634 427 : PGresult *hResult = OGRPG_PQexec(hPGConn, "SHOW search_path");
635 854 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK &&
636 427 : PQntuples(hResult) > 0)
637 : {
638 427 : const char *pszVal = PQgetvalue(hResult, 0, 0);
639 427 : if (pszVal)
640 : {
641 427 : osSearchPath = pszVal;
642 : }
643 : }
644 427 : OGRPGClearResult(hResult);
645 : }
646 :
647 : /* -------------------------------------------------------------------- */
648 : /* Set active schema if different from 'public'. Also add */
649 : /* postgis schema if needed. */
650 : /* -------------------------------------------------------------------- */
651 430 : if (osActiveSchema != "public" ||
652 6 : (!osPostgisSchema.empty() &&
653 3 : osSearchPath.find(osPostgisSchema) == std::string::npos))
654 : {
655 424 : std::string osNewSearchPath;
656 424 : if (!osActiveSchema.empty() && osActiveSchema != "public")
657 : {
658 : osNewSearchPath +=
659 422 : OGRPGEscapeString(hPGConn, osActiveSchema.c_str());
660 : }
661 : // SHOW search_path reports "", but SET search_path doesn't accept it!
662 : // It must be transformed to ''
663 424 : if (STARTS_WITH(osSearchPath.c_str(), "\"\""))
664 2 : osSearchPath = "''" + osSearchPath.substr(2);
665 424 : if (!osSearchPath.empty())
666 : {
667 424 : if (!osNewSearchPath.empty())
668 422 : osNewSearchPath += ',';
669 424 : osNewSearchPath += osSearchPath;
670 : }
671 848 : if (!osPostgisSchema.empty() &&
672 424 : osSearchPath.find(osPostgisSchema) == std::string::npos)
673 : {
674 2 : if (!osNewSearchPath.empty())
675 2 : osNewSearchPath += ',';
676 : osNewSearchPath +=
677 2 : OGRPGEscapeString(hPGConn, osPostgisSchema.c_str());
678 : }
679 424 : if (osNewSearchPath != osSearchPath)
680 : {
681 422 : CPLDebug("PG", "Modifying search_path from %s to %s",
682 : osSearchPath.c_str(), osNewSearchPath.c_str());
683 :
684 422 : std::string osCommand = "SET search_path=" + osNewSearchPath;
685 422 : PGresult *hResult = OGRPG_PQexec(hPGConn, osCommand.c_str());
686 :
687 422 : if (!hResult || PQresultStatus(hResult) != PGRES_COMMAND_OK)
688 : {
689 0 : OGRPGClearResult(hResult);
690 0 : CPLDebug("PG",
691 : "Command \"%s\" failed. Trying without 'public'.",
692 : osCommand.c_str());
693 : osCommand = CPLSPrintf(
694 : "SET search_path=%s",
695 0 : OGRPGEscapeString(hPGConn, osActiveSchema.c_str()).c_str());
696 0 : PGresult *hResult2 = OGRPG_PQexec(hPGConn, osCommand.c_str());
697 :
698 0 : if (!hResult2 || PQresultStatus(hResult2) != PGRES_COMMAND_OK)
699 : {
700 0 : OGRPGClearResult(hResult2);
701 :
702 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
703 0 : PQerrorMessage(hPGConn));
704 :
705 0 : return FALSE;
706 : }
707 : }
708 :
709 422 : OGRPGClearResult(hResult);
710 : }
711 : }
712 :
713 : /* -------------------------------------------------------------------- */
714 : /* Find out PostgreSQL version */
715 : /* -------------------------------------------------------------------- */
716 427 : sPostgreSQLVersion.nMajor = -1;
717 427 : sPostgreSQLVersion.nMinor = -1;
718 427 : sPostgreSQLVersion.nRelease = -1;
719 :
720 427 : PGresult *hResult = OGRPG_PQexec(hPGConn, "SELECT version()");
721 854 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK &&
722 427 : PQntuples(hResult) > 0)
723 : {
724 427 : char *pszVer = PQgetvalue(hResult, 0, 0);
725 :
726 427 : CPLDebug("PG", "PostgreSQL version string : '%s'", pszVer);
727 :
728 : /* Should work with "PostgreSQL X.Y.Z ..." or "EnterpriseDB X.Y.Z ..."
729 : */
730 427 : const char *pszSpace = strchr(pszVer, ' ');
731 427 : if (pszSpace != nullptr &&
732 427 : isdigit(static_cast<unsigned char>(pszSpace[1])))
733 : {
734 427 : OGRPGDecodeVersionString(&sPostgreSQLVersion, pszSpace + 1);
735 : }
736 : }
737 427 : OGRPGClearResult(hResult);
738 427 : CPLAssert(nullptr ==
739 : hResult); /* Test if safe PQclear has not been broken */
740 :
741 : /* -------------------------------------------------------------------- */
742 : /* Set standard_conforming_strings=ON */
743 : /* -------------------------------------------------------------------- */
744 :
745 427 : hResult = OGRPG_PQexec(hPGConn, "SET standard_conforming_strings = ON");
746 427 : if (!(hResult && PQresultStatus(hResult) == PGRES_COMMAND_OK))
747 : {
748 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", PQerrorMessage(hPGConn));
749 0 : OGRPGClearResult(hResult);
750 0 : return FALSE;
751 : }
752 427 : OGRPGClearResult(hResult);
753 :
754 : /* -------------------------------------------------------------------- */
755 : /* Test if time binary format is int8 or float8 */
756 : /* -------------------------------------------------------------------- */
757 : #if defined(BINARY_CURSOR_ENABLED)
758 : if (bUseBinaryCursor)
759 : {
760 : SoftStartTransaction();
761 :
762 : hResult =
763 : OGRPG_PQexec(hPGConn, "DECLARE gettimebinaryformat BINARY CURSOR "
764 : "FOR SELECT CAST ('00:00:01' AS time)");
765 :
766 : if (hResult && PQresultStatus(hResult) == PGRES_COMMAND_OK)
767 : {
768 : OGRPGClearResult(hResult);
769 :
770 : hResult = OGRPG_PQexec(hPGConn, "FETCH ALL IN gettimebinaryformat");
771 :
772 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK &&
773 : PQntuples(hResult) == 1)
774 : {
775 : if (PQfformat(hResult, 0) == 1) // Binary data representation
776 : {
777 : CPLAssert(PQgetlength(hResult, 0, 0) == 8);
778 : unsigned int nVal[2] = {0, 0};
779 : memcpy(nVal, PQgetvalue(hResult, 0, 0), 8);
780 : CPL_MSBPTR32(&nVal[0]);
781 : CPL_MSBPTR32(&nVal[1]);
782 : double dVal = 0.0;
783 : memcpy(&dVal, PQgetvalue(hResult, 0, 0), 8);
784 : CPL_MSBPTR64(&dVal);
785 : if (nVal[0] == 0 && nVal[1] == 1000000)
786 : {
787 : bBinaryTimeFormatIsInt8 = TRUE;
788 : CPLDebug("PG", "Time binary format is int8");
789 : }
790 : else if (dVal == 1.)
791 : {
792 : bBinaryTimeFormatIsInt8 = FALSE;
793 : CPLDebug("PG", "Time binary format is float8");
794 : }
795 : else
796 : {
797 : bBinaryTimeFormatIsInt8 = FALSE;
798 : CPLDebug("PG", "Time binary format is unknown");
799 : }
800 : }
801 : }
802 : }
803 :
804 : OGRPGClearResult(hResult);
805 :
806 : hResult = OGRPG_PQexec(hPGConn, "CLOSE gettimebinaryformat");
807 : OGRPGClearResult(hResult);
808 :
809 : SoftCommitTransaction();
810 : }
811 : #endif
812 :
813 : /* -------------------------------------------------------------------- */
814 : /* Test to see if this database instance has support for the */
815 : /* PostGIS Geometry type. If so, disable sequential scanning */
816 : /* so we will get the value of the gist indexes. */
817 : /* -------------------------------------------------------------------- */
818 427 : hResult =
819 427 : OGRPG_PQexec(hPGConn, "SELECT oid, typname FROM pg_type WHERE typname "
820 : "IN ('geometry', 'geography') AND typtype='b'");
821 :
822 427 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK &&
823 1281 : PQntuples(hResult) > 0 &&
824 427 : CPLTestBool(CPLGetConfigOption("PG_USE_POSTGIS", "YES")))
825 : {
826 711 : for (int iRecord = 0; iRecord < PQntuples(hResult); iRecord++)
827 : {
828 474 : const char *pszOid = PQgetvalue(hResult, iRecord, 0);
829 474 : const char *pszTypname = PQgetvalue(hResult, iRecord, 1);
830 474 : if (EQUAL(pszTypname, "geometry"))
831 : {
832 237 : bHavePostGIS = TRUE;
833 237 : nGeometryOID = static_cast<Oid>(strtoul(pszOid, nullptr, 10));
834 : }
835 237 : else if (CPLTestBool(CPLGetConfigOption("PG_USE_GEOGRAPHY", "YES")))
836 : {
837 237 : bHaveGeography = TRUE;
838 237 : nGeographyOID = static_cast<Oid>(strtoul(pszOid, nullptr, 10));
839 : }
840 : }
841 : }
842 :
843 427 : OGRPGClearResult(hResult);
844 :
845 : /* -------------------------------------------------------------------- */
846 : /* Find out PostGIS version */
847 : /* -------------------------------------------------------------------- */
848 :
849 427 : sPostGISVersion.nMajor = -1;
850 427 : sPostGISVersion.nMinor = -1;
851 427 : sPostGISVersion.nRelease = -1;
852 :
853 427 : if (bHavePostGIS)
854 : {
855 237 : hResult = OGRPG_PQexec(hPGConn, "SELECT postgis_version()");
856 474 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK &&
857 237 : PQntuples(hResult) > 0)
858 : {
859 237 : char *pszVer = PQgetvalue(hResult, 0, 0);
860 :
861 237 : CPLDebug("PG", "PostGIS version string : '%s'", pszVer);
862 :
863 237 : OGRPGDecodeVersionString(&sPostGISVersion, pszVer);
864 : }
865 237 : OGRPGClearResult(hResult);
866 : }
867 :
868 427 : m_bHasGeometryColumns =
869 427 : OGRPG_Check_Table_Exists(hPGConn, "geometry_columns");
870 427 : m_bHasSpatialRefSys = OGRPG_Check_Table_Exists(hPGConn, "spatial_ref_sys");
871 :
872 : /* -------------------------------------------------------------------- */
873 : /* Find out "unknown SRID" value */
874 : /* -------------------------------------------------------------------- */
875 :
876 427 : if (sPostGISVersion.nMajor >= 2)
877 : {
878 237 : hResult =
879 237 : OGRPG_PQexec(hPGConn, "SELECT ST_Srid('POINT EMPTY'::GEOMETRY)");
880 :
881 474 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK &&
882 237 : PQntuples(hResult) > 0)
883 : {
884 237 : nUndefinedSRID = atoi(PQgetvalue(hResult, 0, 0));
885 : }
886 :
887 237 : OGRPGClearResult(hResult);
888 : }
889 : else
890 190 : nUndefinedSRID = -1;
891 :
892 427 : GetCurrentSchema();
893 :
894 854 : bListAllTables = CPLTestBool(
895 427 : CSLFetchNameValueDef(papszOpenOptions, "LIST_ALL_TABLES",
896 : CPLGetConfigOption("PG_LIST_ALL_TABLES", "NO")));
897 :
898 854 : m_bSkipViews = CPLTestBool(
899 427 : CSLFetchNameValueDef(papszOpenOptions, "SKIP_VIEWS",
900 : CPLGetConfigOption("PG_SKIP_VIEWS", "NO")));
901 :
902 427 : return TRUE;
903 : }
904 :
905 : /************************************************************************/
906 : /* LoadTables() */
907 : /************************************************************************/
908 :
909 1290 : void OGRPGDataSource::LoadTables()
910 : {
911 1290 : if (bHasLoadTables)
912 1006 : return;
913 284 : bHasLoadTables = TRUE;
914 :
915 284 : PGTableEntry **papsTables = nullptr;
916 284 : int nTableCount = 0;
917 284 : CPLHashSet *hSetTables = nullptr;
918 568 : std::set<CPLString> osRegisteredLayers;
919 :
920 318 : for (int i = 0; i < nLayers; i++)
921 : {
922 34 : osRegisteredLayers.insert(papoLayers[i]->GetName());
923 : }
924 :
925 284 : if (pszForcedTables)
926 : {
927 : const CPLStringList aosTableList(
928 20 : CSLTokenizeString2(pszForcedTables, ",", CSLT_HONOURSTRINGS));
929 :
930 22 : for (const char *pszTable : aosTableList)
931 : {
932 : // Get schema and table name
933 : const CPLStringList aosQualifiedParts(
934 24 : CSLTokenizeString2(pszTable, ".", CSLT_HONOURSTRINGS));
935 12 : const int nParts = aosQualifiedParts.size();
936 :
937 12 : if (nParts == 1 || nParts == 2)
938 : {
939 : /* Find the geometry column name if specified */
940 24 : std::string osTableName;
941 12 : std::string osGeomColumnName;
942 12 : const char *pszTablePart = aosQualifiedParts[nParts - 1];
943 12 : int j = 0;
944 210 : for (; pszTablePart[j]; ++j)
945 : {
946 200 : if (pszTablePart[j] == '\\')
947 : {
948 8 : if (pszTablePart[j + 1] == '\\' ||
949 4 : pszTablePart[j + 1] == '(')
950 : {
951 8 : ++j;
952 8 : osTableName += pszTablePart[j];
953 : }
954 : else
955 : {
956 0 : CPLError(CE_Failure, CPLE_AppDefined,
957 : "Unexpected character after backslash at "
958 : "offset %d of %s",
959 : j, pszTablePart);
960 : }
961 : }
962 192 : else if (pszTablePart[j] == '(')
963 : {
964 : // Now parse the geometry column name
965 2 : break;
966 : }
967 : else
968 : {
969 190 : osTableName += pszTablePart[j];
970 : }
971 : }
972 :
973 12 : if (pszTablePart[j] == '(')
974 : {
975 : // Now parse the geometry column name
976 2 : ++j;
977 20 : for (; pszTablePart[j]; ++j)
978 : {
979 18 : if (pszTablePart[j] == '\\')
980 : {
981 0 : if (pszTablePart[j + 1] == '\\' ||
982 0 : pszTablePart[j + 1] == '(')
983 : {
984 0 : ++j;
985 0 : osGeomColumnName += pszTablePart[j];
986 : }
987 : else
988 : {
989 0 : CPLError(CE_Failure, CPLE_AppDefined,
990 : "Unexpected character after backslash "
991 : "at offset %d of %s",
992 : j, pszTablePart);
993 : }
994 : }
995 : else
996 : {
997 18 : osGeomColumnName += pszTablePart[j];
998 : }
999 : }
1000 4 : if (!osGeomColumnName.empty() &&
1001 2 : osGeomColumnName.back() == ')')
1002 2 : osGeomColumnName.pop_back();
1003 : }
1004 :
1005 24 : papsTables = static_cast<PGTableEntry **>(CPLRealloc(
1006 12 : papsTables, sizeof(PGTableEntry *) * (nTableCount + 1)));
1007 24 : papsTables[nTableCount] = static_cast<PGTableEntry *>(
1008 12 : CPLCalloc(1, sizeof(PGTableEntry)));
1009 12 : if (!osGeomColumnName.empty())
1010 2 : OGRPGTableEntryAddGeomColumn(papsTables[nTableCount],
1011 : osGeomColumnName.c_str());
1012 :
1013 12 : if (nParts == 2)
1014 : {
1015 0 : papsTables[nTableCount]->pszSchemaName =
1016 0 : CPLStrdup(aosQualifiedParts[0]);
1017 0 : papsTables[nTableCount]->pszTableName =
1018 0 : CPLStrdup(osTableName.c_str());
1019 : }
1020 : else
1021 : {
1022 24 : papsTables[nTableCount]->pszSchemaName =
1023 12 : CPLStrdup(osActiveSchema.c_str());
1024 24 : papsTables[nTableCount]->pszTableName =
1025 12 : CPLStrdup(osTableName.c_str());
1026 : }
1027 12 : nTableCount++;
1028 : }
1029 : }
1030 : }
1031 :
1032 : /* -------------------------------------------------------------------- */
1033 : /* Get a list of available tables if they have not been */
1034 : /* specified through the TABLES connection string param */
1035 : /* -------------------------------------------------------------------- */
1036 284 : const char *pszAllowedRelations = m_bSkipViews ? "'r'" : "'r','v','m','f'";
1037 :
1038 284 : hSetTables = CPLHashSetNew(OGRPGHashTableEntry, OGRPGEqualTableEntry,
1039 : OGRPGFreeTableEntry);
1040 :
1041 274 : if (nTableCount == 0 && bHavePostGIS && sPostGISVersion.nMajor >= 2 &&
1042 711 : !bListAllTables &&
1043 : /* Config option mostly for comparison/debugging/etc... */
1044 153 : CPLTestBool(CPLGetConfigOption("PG_USE_POSTGIS2_OPTIM", "YES")))
1045 : {
1046 : /* --------------------------------------------------------------------
1047 : */
1048 : /* With PostGIS 2.0, the geometry_columns and geography_columns */
1049 : /* are views, based on the catalog system, that can be slow to */
1050 : /* query, so query directly the catalog system. */
1051 : /* See http://trac.osgeo.org/postgis/ticket/3092 */
1052 : /* --------------------------------------------------------------------
1053 : */
1054 152 : CPLString osCommand;
1055 304 : const char *pszConstraintDef = sPostgreSQLVersion.nMajor >= 12
1056 152 : ? "pg_get_constraintdef(s.oid)"
1057 : : "s.consrc";
1058 : osCommand.Printf(
1059 : "SELECT c.relname, n.nspname, c.relkind, a.attname, t.typname, "
1060 : "postgis_typmod_dims(a.atttypmod) dim, "
1061 : "postgis_typmod_srid(a.atttypmod) srid, "
1062 : "postgis_typmod_type(a.atttypmod)::text geomtyp, "
1063 : "array_agg(%s)::text att_constraints, a.attnotnull, "
1064 : "d.description "
1065 : "FROM pg_class c JOIN pg_attribute a ON a.attrelid=c.oid "
1066 : "JOIN pg_namespace n ON c.relnamespace = n.oid "
1067 : "AND c.relkind in (%s) AND NOT ( n.nspname = 'public' AND "
1068 : "c.relname = 'raster_columns' ) "
1069 : "JOIN pg_type t ON a.atttypid = t.oid AND (t.typname = "
1070 : "'geometry'::name OR t.typname = 'geography'::name) "
1071 : "LEFT JOIN pg_constraint s ON s.connamespace = n.oid AND "
1072 : "s.conrelid = c.oid "
1073 : "AND a.attnum = ANY (s.conkey) "
1074 : "AND (%s LIKE '%%geometrytype(%% = %%' OR %s LIKE '%%ndims(%% = "
1075 : "%%' OR %s LIKE '%%srid(%% = %%') "
1076 : "LEFT JOIN pg_description d ON d.objoid = c.oid AND d.classoid = "
1077 : "'pg_class'::regclass::oid AND d.objsubid = 0 "
1078 : "GROUP BY c.relname, n.nspname, c.relkind, a.attname, t.typname, "
1079 : "dim, srid, geomtyp, a.attnotnull, c.oid, a.attnum, d.description "
1080 : "ORDER BY c.oid, a.attnum",
1081 : pszConstraintDef, pszAllowedRelations, pszConstraintDef,
1082 152 : pszConstraintDef, pszConstraintDef);
1083 152 : PGresult *hResult = OGRPG_PQexec(hPGConn, osCommand.c_str());
1084 :
1085 152 : if (!hResult || PQresultStatus(hResult) != PGRES_TUPLES_OK)
1086 : {
1087 0 : OGRPGClearResult(hResult);
1088 :
1089 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
1090 0 : PQerrorMessage(hPGConn));
1091 0 : goto end;
1092 : }
1093 : /* --------------------------------------------------------------------
1094 : */
1095 : /* Parse the returned table list */
1096 : /* --------------------------------------------------------------------
1097 : */
1098 228 : for (int iRecord = 0; iRecord < PQntuples(hResult); iRecord++)
1099 : {
1100 76 : const char *pszTable = PQgetvalue(hResult, iRecord, 0);
1101 76 : const char *pszSchemaName = PQgetvalue(hResult, iRecord, 1);
1102 76 : const char *pszGeomColumnName = PQgetvalue(hResult, iRecord, 3);
1103 76 : const char *pszGeomOrGeography = PQgetvalue(hResult, iRecord, 4);
1104 76 : const char *pszDim = PQgetvalue(hResult, iRecord, 5);
1105 76 : const char *pszSRID = PQgetvalue(hResult, iRecord, 6);
1106 76 : const char *pszGeomType = PQgetvalue(hResult, iRecord, 7);
1107 76 : const char *pszConstraint = PQgetvalue(hResult, iRecord, 8);
1108 76 : const char *pszNotNull = PQgetvalue(hResult, iRecord, 9);
1109 76 : const char *pszDescription = PQgetvalue(hResult, iRecord, 10);
1110 : /*const char *pszRelkind = PQgetvalue(hResult, iRecord, 2);
1111 : CPLDebug("PG", "%s %s %s %s %s %s %s %s %s %s",
1112 : pszTable, pszSchemaName, pszRelkind,
1113 : pszGeomColumnName, pszGeomOrGeography, pszDim,
1114 : pszSRID, pszGeomType, pszConstraint, pszNotNull);*/
1115 :
1116 76 : int bNullable = EQUAL(pszNotNull, "f");
1117 :
1118 76 : PostgisType ePostgisType = GEOM_TYPE_UNKNOWN;
1119 76 : if (EQUAL(pszGeomOrGeography, "geometry"))
1120 71 : ePostgisType = GEOM_TYPE_GEOMETRY;
1121 5 : else if (EQUAL(pszGeomOrGeography, "geography"))
1122 5 : ePostgisType = GEOM_TYPE_GEOGRAPHY;
1123 :
1124 76 : int nGeomCoordDimension = atoi(pszDim);
1125 76 : bool bHasM = pszGeomType[strlen(pszGeomType) - 1] == 'M';
1126 76 : int nSRID = atoi(pszSRID);
1127 :
1128 : /* Analyze constraints that might override geometrytype, */
1129 : /* coordinate dimension and SRID */
1130 152 : CPLString osConstraint(pszConstraint);
1131 76 : osConstraint = osConstraint.tolower();
1132 76 : pszConstraint = osConstraint.c_str();
1133 76 : const char *pszNeedle = strstr(pszConstraint, "geometrytype(");
1134 152 : CPLString osGeometryType;
1135 76 : if (pszNeedle)
1136 : {
1137 4 : pszNeedle = strchr(pszNeedle, '\'');
1138 4 : if (pszNeedle)
1139 : {
1140 4 : pszNeedle++;
1141 4 : const char *pszEnd = strchr(pszNeedle, '\'');
1142 4 : if (pszEnd)
1143 : {
1144 4 : osGeometryType = pszNeedle;
1145 4 : osGeometryType.resize(pszEnd - pszNeedle);
1146 4 : pszGeomType = osGeometryType.c_str();
1147 4 : bHasM = pszGeomType[strlen(pszGeomType) - 1] == 'M';
1148 : }
1149 : }
1150 : }
1151 :
1152 76 : pszNeedle = strstr(pszConstraint, "srid(");
1153 76 : if (pszNeedle)
1154 : {
1155 4 : pszNeedle = strchr(pszNeedle, '=');
1156 4 : if (pszNeedle)
1157 : {
1158 4 : pszNeedle++;
1159 4 : nSRID = atoi(pszNeedle);
1160 : }
1161 : }
1162 :
1163 76 : pszNeedle = strstr(pszConstraint, "ndims(");
1164 76 : if (pszNeedle)
1165 : {
1166 4 : pszNeedle = strchr(pszNeedle, '=');
1167 4 : if (pszNeedle)
1168 : {
1169 4 : pszNeedle++;
1170 4 : nGeomCoordDimension = atoi(pszNeedle);
1171 : }
1172 : }
1173 :
1174 76 : int GeomTypeFlags = 0;
1175 76 : if (nGeomCoordDimension == 3)
1176 : {
1177 26 : if (bHasM)
1178 0 : GeomTypeFlags |= OGRGeometry::OGR_G_MEASURED;
1179 : else
1180 26 : GeomTypeFlags |= OGRGeometry::OGR_G_3D;
1181 : }
1182 50 : else if (nGeomCoordDimension == 4)
1183 : {
1184 3 : GeomTypeFlags |=
1185 : OGRGeometry::OGR_G_3D | OGRGeometry::OGR_G_MEASURED;
1186 : }
1187 :
1188 152 : papsTables = static_cast<PGTableEntry **>(CPLRealloc(
1189 76 : papsTables, sizeof(PGTableEntry *) * (nTableCount + 1)));
1190 152 : papsTables[nTableCount] =
1191 76 : static_cast<PGTableEntry *>(CPLCalloc(1, sizeof(PGTableEntry)));
1192 76 : papsTables[nTableCount]->pszTableName = CPLStrdup(pszTable);
1193 76 : papsTables[nTableCount]->pszSchemaName = CPLStrdup(pszSchemaName);
1194 152 : papsTables[nTableCount]->pszDescription =
1195 76 : CPLStrdup(pszDescription ? pszDescription : "");
1196 :
1197 76 : OGRPGTableEntryAddGeomColumn(
1198 76 : papsTables[nTableCount], pszGeomColumnName, pszGeomType,
1199 : GeomTypeFlags, nSRID, ePostgisType, bNullable);
1200 76 : nTableCount++;
1201 :
1202 : PGTableEntry *psEntry =
1203 76 : OGRPGFindTableEntry(hSetTables, pszTable, pszSchemaName);
1204 76 : if (psEntry == nullptr)
1205 62 : psEntry = OGRPGAddTableEntry(hSetTables, pszTable,
1206 : pszSchemaName, pszDescription);
1207 76 : OGRPGTableEntryAddGeomColumn(psEntry, pszGeomColumnName,
1208 : pszGeomType, GeomTypeFlags, nSRID,
1209 : ePostgisType, bNullable);
1210 : }
1211 :
1212 152 : OGRPGClearResult(hResult);
1213 : }
1214 132 : else if (nTableCount == 0)
1215 : {
1216 122 : CPLString osCommand;
1217 :
1218 : /* Caution : in PostGIS case, the result has 11 columns, whereas in the
1219 : */
1220 : /* non-PostGIS case it has only 3 columns */
1221 122 : if (bHavePostGIS && !bListAllTables)
1222 : {
1223 : osCommand.Printf(
1224 : "SELECT c.relname, n.nspname, g.f_geometry_column, "
1225 : "g.type, g.coord_dimension, g.srid, %d, a.attnotnull, "
1226 : "d.description, c.oid as oid, a.attnum as attnum "
1227 : "FROM pg_class c "
1228 : "JOIN pg_namespace n ON c.relnamespace=n.oid "
1229 : "JOIN geometry_columns g "
1230 : "ON c.relname::TEXT = g.f_table_name::TEXT AND n.nspname = "
1231 : "g.f_table_schema "
1232 : "JOIN pg_attribute a "
1233 : "ON a.attname = g.f_geometry_column AND a.attrelid = c.oid "
1234 : "LEFT JOIN pg_description d "
1235 : "ON d.objoid = c.oid AND d.classoid = "
1236 : "'pg_class'::regclass::oid AND d.objsubid = 0 "
1237 : "WHERE c.relkind in (%s) ",
1238 1 : GEOM_TYPE_GEOMETRY, pszAllowedRelations);
1239 :
1240 1 : if (bHaveGeography)
1241 2 : osCommand += CPLString().Printf(
1242 : "UNION SELECT c.relname, n.nspname, "
1243 : "g.f_geography_column, "
1244 : "g.type, g.coord_dimension, g.srid, %d, a.attnotnull, "
1245 : "d.description, c.oid as oid, a.attnum as attnum "
1246 : "FROM pg_class c "
1247 : "JOIN pg_namespace n ON c.relnamespace=n.oid "
1248 : "JOIN geography_columns g "
1249 : "ON c.relname::TEXT = g.f_table_name::TEXT AND n.nspname = "
1250 : "g.f_table_schema "
1251 : "JOIN pg_attribute a "
1252 : "ON a.attname = g.f_geography_column AND a.attrelid = "
1253 : "c.oid "
1254 : "LEFT JOIN pg_description d "
1255 : "ON d.objoid = c.oid AND d.classoid = "
1256 : "'pg_class'::regclass::oid AND d.objsubid = 0 "
1257 : "WHERE c.relkind in (%s)",
1258 1 : GEOM_TYPE_GEOGRAPHY, pszAllowedRelations);
1259 1 : osCommand += " ORDER BY oid, attnum";
1260 : }
1261 : else
1262 : osCommand.Printf("SELECT c.relname, n.nspname, d.description FROM "
1263 : "pg_class c "
1264 : "JOIN pg_namespace n ON c.relnamespace=n.oid "
1265 : "LEFT JOIN pg_description d "
1266 : "ON d.objoid = c.oid AND d.classoid = "
1267 : "'pg_class'::regclass::oid AND d.objsubid = 0 "
1268 : "WHERE (c.relkind in (%s) AND "
1269 : "c.relname !~ '^pg_')",
1270 121 : pszAllowedRelations);
1271 :
1272 122 : PGresult *hResult = OGRPG_PQexec(hPGConn, osCommand.c_str());
1273 :
1274 122 : if (!hResult || PQresultStatus(hResult) != PGRES_TUPLES_OK)
1275 : {
1276 0 : OGRPGClearResult(hResult);
1277 :
1278 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
1279 0 : PQerrorMessage(hPGConn));
1280 0 : goto end;
1281 : }
1282 :
1283 : /* --------------------------------------------------------------------
1284 : */
1285 : /* Parse the returned table list */
1286 : /* --------------------------------------------------------------------
1287 : */
1288 8626 : for (int iRecord = 0; iRecord < PQntuples(hResult); iRecord++)
1289 : {
1290 8504 : const char *pszTable = PQgetvalue(hResult, iRecord, 0);
1291 8504 : const char *pszSchemaName = PQgetvalue(hResult, iRecord, 1);
1292 8504 : const char *pszGeomColumnName = nullptr;
1293 8504 : const char *pszGeomType = nullptr;
1294 8504 : const char *pszDescription = nullptr;
1295 8504 : int nGeomCoordDimension = 0;
1296 8504 : bool bHasM = false;
1297 8504 : int nSRID = 0;
1298 8504 : int bNullable = TRUE;
1299 8504 : PostgisType ePostgisType = GEOM_TYPE_UNKNOWN;
1300 8504 : if (bHavePostGIS && !bListAllTables)
1301 : {
1302 2 : pszGeomColumnName = PQgetvalue(hResult, iRecord, 2);
1303 2 : pszGeomType = PQgetvalue(hResult, iRecord, 3);
1304 2 : bHasM = pszGeomType[strlen(pszGeomType) - 1] == 'M';
1305 2 : nGeomCoordDimension = atoi(PQgetvalue(hResult, iRecord, 4));
1306 2 : nSRID = atoi(PQgetvalue(hResult, iRecord, 5));
1307 2 : ePostgisType = static_cast<PostgisType>(
1308 2 : atoi(PQgetvalue(hResult, iRecord, 6)));
1309 2 : bNullable = EQUAL(PQgetvalue(hResult, iRecord, 7), "f");
1310 2 : pszDescription = PQgetvalue(hResult, iRecord, 8);
1311 : }
1312 : else
1313 : {
1314 8502 : pszDescription = PQgetvalue(hResult, iRecord, 2);
1315 : }
1316 :
1317 8504 : if (EQUAL(pszTable, "spatial_ref_sys") ||
1318 8383 : EQUAL(pszTable, "geometry_columns") ||
1319 8264 : EQUAL(pszTable, "geography_columns"))
1320 359 : continue;
1321 :
1322 8145 : if (EQUAL(pszSchemaName, "information_schema") ||
1323 402 : EQUAL(pszSchemaName, "ogr_system_tables"))
1324 7855 : continue;
1325 :
1326 290 : int GeomTypeFlags = 0;
1327 290 : if (nGeomCoordDimension == 3)
1328 : {
1329 1 : if (bHasM)
1330 0 : GeomTypeFlags |= OGRGeometry::OGR_G_MEASURED;
1331 : else
1332 1 : GeomTypeFlags |= OGRGeometry::OGR_G_3D;
1333 : }
1334 289 : else if (nGeomCoordDimension == 4)
1335 : {
1336 0 : GeomTypeFlags |=
1337 : OGRGeometry::OGR_G_3D | OGRGeometry::OGR_G_MEASURED;
1338 : }
1339 :
1340 580 : papsTables = static_cast<PGTableEntry **>(CPLRealloc(
1341 290 : papsTables, sizeof(PGTableEntry *) * (nTableCount + 1)));
1342 580 : papsTables[nTableCount] =
1343 290 : static_cast<PGTableEntry *>(CPLCalloc(1, sizeof(PGTableEntry)));
1344 290 : papsTables[nTableCount]->pszTableName = CPLStrdup(pszTable);
1345 290 : papsTables[nTableCount]->pszSchemaName = CPLStrdup(pszSchemaName);
1346 580 : papsTables[nTableCount]->pszDescription =
1347 290 : CPLStrdup(pszDescription ? pszDescription : "");
1348 290 : if (pszGeomColumnName)
1349 2 : OGRPGTableEntryAddGeomColumn(
1350 2 : papsTables[nTableCount], pszGeomColumnName, pszGeomType,
1351 : GeomTypeFlags, nSRID, ePostgisType, bNullable);
1352 290 : nTableCount++;
1353 :
1354 : PGTableEntry *psEntry =
1355 290 : OGRPGFindTableEntry(hSetTables, pszTable, pszSchemaName);
1356 290 : if (psEntry == nullptr)
1357 290 : psEntry = OGRPGAddTableEntry(hSetTables, pszTable,
1358 : pszSchemaName, pszDescription);
1359 290 : if (pszGeomColumnName)
1360 2 : OGRPGTableEntryAddGeomColumn(psEntry, pszGeomColumnName,
1361 : pszGeomType, GeomTypeFlags, nSRID,
1362 : ePostgisType, bNullable);
1363 : }
1364 :
1365 : /* --------------------------------------------------------------------
1366 : */
1367 : /* Cleanup */
1368 : /* --------------------------------------------------------------------
1369 : */
1370 122 : OGRPGClearResult(hResult);
1371 : }
1372 :
1373 : /* -------------------------------------------------------------------- */
1374 : /* Register the available tables. */
1375 : /* -------------------------------------------------------------------- */
1376 662 : for (int iRecord = 0; iRecord < nTableCount; iRecord++)
1377 : {
1378 : const PGTableEntry *psEntry = static_cast<PGTableEntry *>(
1379 378 : CPLHashSetLookup(hSetTables, papsTables[iRecord]));
1380 :
1381 : /* If SCHEMAS= is specified, only take into account tables inside */
1382 : /* one of the specified schemas */
1383 750 : if (papszSchemaList != nullptr &&
1384 372 : CSLFindString(papszSchemaList,
1385 372 : papsTables[iRecord]->pszSchemaName) == -1)
1386 : {
1387 284 : continue;
1388 : }
1389 :
1390 132 : CPLString osDefnName;
1391 :
1392 264 : if (papsTables[iRecord]->pszSchemaName &&
1393 132 : osCurrentSchema != papsTables[iRecord]->pszSchemaName)
1394 : {
1395 6 : osDefnName.Printf("%s.%s", papsTables[iRecord]->pszSchemaName,
1396 6 : papsTables[iRecord]->pszTableName);
1397 : }
1398 : else
1399 : {
1400 : // no prefix for current_schema in layer name, for backwards
1401 : // compatibility
1402 126 : osDefnName = papsTables[iRecord]->pszTableName;
1403 : }
1404 132 : if (osRegisteredLayers.find(osDefnName) != osRegisteredLayers.end())
1405 38 : continue;
1406 94 : osRegisteredLayers.insert(std::move(osDefnName));
1407 :
1408 188 : OGRPGTableLayer *poLayer = OpenTable(
1409 94 : osCurrentSchema, papsTables[iRecord]->pszTableName,
1410 94 : papsTables[iRecord]->pszSchemaName,
1411 94 : papsTables[iRecord]->pszDescription, nullptr, bDSUpdate, FALSE);
1412 94 : if (psEntry != nullptr)
1413 : {
1414 82 : if (psEntry->nGeomColumnCount > 0)
1415 : {
1416 44 : poLayer->SetGeometryInformation(psEntry->pasGeomColumns,
1417 44 : psEntry->nGeomColumnCount);
1418 : }
1419 : }
1420 : else
1421 : {
1422 12 : if (papsTables[iRecord]->nGeomColumnCount > 0)
1423 : {
1424 2 : poLayer->SetGeometryInformation(
1425 2 : papsTables[iRecord]->pasGeomColumns,
1426 2 : papsTables[iRecord]->nGeomColumnCount);
1427 : }
1428 : }
1429 : }
1430 :
1431 284 : end:
1432 284 : if (hSetTables)
1433 284 : CPLHashSetDestroy(hSetTables);
1434 :
1435 662 : for (int i = 0; i < nTableCount; i++)
1436 378 : OGRPGFreeTableEntry(papsTables[i]);
1437 284 : CPLFree(papsTables);
1438 : }
1439 :
1440 : /************************************************************************/
1441 : /* OpenTable() */
1442 : /************************************************************************/
1443 :
1444 512 : OGRPGTableLayer *OGRPGDataSource::OpenTable(CPLString &osCurrentSchemaIn,
1445 : const char *pszNewName,
1446 : const char *pszSchemaName,
1447 : const char *pszDescription,
1448 : const char *pszGeomColumnForced,
1449 : int bUpdate, int bTestOpen)
1450 :
1451 : {
1452 : /* -------------------------------------------------------------------- */
1453 : /* Create the layer object. */
1454 : /* -------------------------------------------------------------------- */
1455 : OGRPGTableLayer *poLayer =
1456 : new OGRPGTableLayer(this, osCurrentSchemaIn, pszNewName, pszSchemaName,
1457 512 : pszDescription, pszGeomColumnForced, bUpdate);
1458 512 : if (bTestOpen && !(poLayer->ReadTableDefinition()))
1459 : {
1460 234 : delete poLayer;
1461 234 : return nullptr;
1462 : }
1463 :
1464 : /* -------------------------------------------------------------------- */
1465 : /* Add layer to data source layer list. */
1466 : /* -------------------------------------------------------------------- */
1467 278 : papoLayers = static_cast<OGRPGTableLayer **>(
1468 278 : CPLRealloc(papoLayers, sizeof(OGRPGTableLayer *) * (nLayers + 1)));
1469 278 : papoLayers[nLayers++] = poLayer;
1470 :
1471 278 : return poLayer;
1472 : }
1473 :
1474 : /************************************************************************/
1475 : /* DeleteLayer() */
1476 : /************************************************************************/
1477 :
1478 24 : OGRErr OGRPGDataSource::DeleteLayer(int iLayer)
1479 :
1480 : {
1481 : /* Force loading of all registered tables */
1482 24 : GetLayerCount();
1483 24 : if (iLayer < 0 || iLayer >= nLayers)
1484 0 : return OGRERR_FAILURE;
1485 :
1486 24 : EndCopy();
1487 :
1488 : /* -------------------------------------------------------------------- */
1489 : /* Blow away our OGR structures related to the layer. This is */
1490 : /* pretty dangerous if anything has a reference to this layer! */
1491 : /* -------------------------------------------------------------------- */
1492 48 : CPLString osLayerName = papoLayers[iLayer]->GetLayerDefn()->GetName();
1493 48 : CPLString osTableName = papoLayers[iLayer]->GetTableName();
1494 48 : CPLString osSchemaName = papoLayers[iLayer]->GetSchemaName();
1495 :
1496 24 : CPLDebug("PG", "DeleteLayer(%s)", osLayerName.c_str());
1497 :
1498 24 : delete papoLayers[iLayer];
1499 24 : memmove(papoLayers + iLayer, papoLayers + iLayer + 1,
1500 24 : sizeof(void *) * (nLayers - iLayer - 1));
1501 24 : nLayers--;
1502 :
1503 24 : if (osLayerName.empty())
1504 0 : return OGRERR_NONE;
1505 :
1506 : /* -------------------------------------------------------------------- */
1507 : /* Remove from the database. */
1508 : /* -------------------------------------------------------------------- */
1509 24 : CPLString osCommand;
1510 :
1511 24 : SoftStartTransaction();
1512 :
1513 : osCommand.Printf("DROP TABLE %s.%s CASCADE",
1514 48 : OGRPGEscapeColumnName(osSchemaName).c_str(),
1515 72 : OGRPGEscapeColumnName(osTableName).c_str());
1516 24 : PGresult *hResult = OGRPG_PQexec(hPGConn, osCommand.c_str());
1517 24 : OGRPGClearResult(hResult);
1518 :
1519 24 : SoftCommitTransaction();
1520 :
1521 24 : return OGRERR_NONE;
1522 : }
1523 :
1524 : /************************************************************************/
1525 : /* ICreateLayer() */
1526 : /************************************************************************/
1527 :
1528 233 : OGRLayer *OGRPGDataSource::ICreateLayer(const char *pszLayerName,
1529 : const OGRGeomFieldDefn *poGeomFieldDefn,
1530 : CSLConstList papszOptions)
1531 :
1532 : {
1533 233 : const char *pszGeomType = nullptr;
1534 233 : char *pszTableName = nullptr;
1535 233 : char *pszSchemaName = nullptr;
1536 233 : int GeometryTypeFlags = 0;
1537 :
1538 233 : auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
1539 : const auto poSRS =
1540 233 : poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
1541 :
1542 233 : if (pszLayerName == nullptr)
1543 0 : return nullptr;
1544 :
1545 233 : EndCopy();
1546 :
1547 : const bool bUTF8ToASCII =
1548 233 : CPLFetchBool(papszOptions, "LAUNDER_ASCII", false);
1549 : const bool bLaunder =
1550 233 : bUTF8ToASCII || CPLFetchBool(papszOptions, "LAUNDER", true);
1551 :
1552 233 : const char *pszFIDColumnNameIn = CSLFetchNameValue(papszOptions, "FID");
1553 466 : CPLString osFIDColumnName;
1554 233 : if (pszFIDColumnNameIn == nullptr)
1555 231 : osFIDColumnName = "ogc_fid";
1556 : else
1557 : {
1558 2 : if (bLaunder)
1559 : {
1560 : char *pszLaunderedFid =
1561 2 : OGRPGCommonLaunderName(pszFIDColumnNameIn, "PG", bUTF8ToASCII);
1562 2 : osFIDColumnName += pszLaunderedFid;
1563 2 : CPLFree(pszLaunderedFid);
1564 : }
1565 : else
1566 0 : osFIDColumnName += pszFIDColumnNameIn;
1567 : }
1568 466 : CPLString osFIDColumnNameEscaped = OGRPGEscapeColumnName(osFIDColumnName);
1569 :
1570 233 : if (STARTS_WITH(pszLayerName, "pg"))
1571 : {
1572 0 : CPLError(CE_Warning, CPLE_AppDefined,
1573 : "The layer name should not begin by 'pg' as it is a reserved "
1574 : "prefix");
1575 : }
1576 :
1577 233 : if (OGR_GT_HasZ(eType))
1578 5 : GeometryTypeFlags |= OGRGeometry::OGR_G_3D;
1579 233 : if (OGR_GT_HasM(eType))
1580 2 : GeometryTypeFlags |= OGRGeometry::OGR_G_MEASURED;
1581 :
1582 233 : int ForcedGeometryTypeFlags = -1;
1583 233 : const char *pszDim = CSLFetchNameValue(papszOptions, "DIM");
1584 233 : if (pszDim != nullptr)
1585 : {
1586 75 : if (EQUAL(pszDim, "XY") || EQUAL(pszDim, "2"))
1587 : {
1588 0 : GeometryTypeFlags = 0;
1589 0 : ForcedGeometryTypeFlags = GeometryTypeFlags;
1590 : }
1591 75 : else if (EQUAL(pszDim, "XYZ") || EQUAL(pszDim, "3"))
1592 : {
1593 69 : GeometryTypeFlags = OGRGeometry::OGR_G_3D;
1594 69 : ForcedGeometryTypeFlags = GeometryTypeFlags;
1595 : }
1596 6 : else if (EQUAL(pszDim, "XYM"))
1597 : {
1598 3 : GeometryTypeFlags = OGRGeometry::OGR_G_MEASURED;
1599 3 : ForcedGeometryTypeFlags = GeometryTypeFlags;
1600 : }
1601 3 : else if (EQUAL(pszDim, "XYZM") || EQUAL(pszDim, "4"))
1602 : {
1603 3 : GeometryTypeFlags =
1604 : OGRGeometry::OGR_G_3D | OGRGeometry::OGR_G_MEASURED;
1605 3 : ForcedGeometryTypeFlags = GeometryTypeFlags;
1606 : }
1607 : else
1608 : {
1609 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for DIM");
1610 : }
1611 : }
1612 :
1613 : /* Should we turn layers with None geometry type as Unknown/GEOMETRY */
1614 : /* so they are still recorded in geometry_columns table ? (#4012) */
1615 233 : int bNoneAsUnknown = CPLTestBool(
1616 233 : CSLFetchNameValueDef(papszOptions, "NONE_AS_UNKNOWN", "NO"));
1617 233 : if (bNoneAsUnknown && eType == wkbNone)
1618 0 : eType = wkbUnknown;
1619 :
1620 233 : int bExtractSchemaFromLayerName = CPLTestBool(CSLFetchNameValueDef(
1621 233 : papszOptions, "EXTRACT_SCHEMA_FROM_LAYER_NAME", "YES"));
1622 :
1623 : /* Postgres Schema handling:
1624 : Extract schema name from input layer name or passed with -lco SCHEMA.
1625 : Set layer name to "schema.table" or to "table" if schema ==
1626 : current_schema() Usage without schema name is backwards compatible
1627 : */
1628 233 : const char *pszDotPos = strstr(pszLayerName, ".");
1629 233 : if (pszDotPos != nullptr && bExtractSchemaFromLayerName)
1630 : {
1631 19 : int length = static_cast<int>(pszDotPos - pszLayerName);
1632 19 : pszSchemaName = static_cast<char *>(CPLMalloc(length + 1));
1633 19 : strncpy(pszSchemaName, pszLayerName, length);
1634 19 : pszSchemaName[length] = '\0';
1635 :
1636 19 : if (bLaunder)
1637 17 : pszTableName = OGRPGCommonLaunderName(pszDotPos + 1, "PG",
1638 : bUTF8ToASCII); // skip "."
1639 : else
1640 2 : pszTableName = CPLStrdup(
1641 4 : OGRPGCommonGenerateShortEnoughIdentifier(pszDotPos + 1)
1642 19 : .c_str()); // skip "."
1643 : }
1644 : else
1645 : {
1646 214 : pszSchemaName = nullptr;
1647 214 : if (bLaunder)
1648 214 : pszTableName = OGRPGCommonLaunderName(pszLayerName, "PG",
1649 : bUTF8ToASCII); // skip "."
1650 : else
1651 : pszTableName =
1652 0 : CPLStrdup(OGRPGCommonGenerateShortEnoughIdentifier(pszLayerName)
1653 : .c_str()); // skip "."
1654 : }
1655 :
1656 : /* -------------------------------------------------------------------- */
1657 : /* Set the default schema for the layers. */
1658 : /* -------------------------------------------------------------------- */
1659 233 : if (CSLFetchNameValue(papszOptions, "SCHEMA") != nullptr)
1660 : {
1661 3 : CPLFree(pszSchemaName);
1662 3 : pszSchemaName = CPLStrdup(CSLFetchNameValue(papszOptions, "SCHEMA"));
1663 : }
1664 :
1665 233 : const bool bTemporary = CPLFetchBool(papszOptions, "TEMPORARY", false);
1666 233 : if (bTemporary)
1667 : {
1668 1 : CPLFree(pszSchemaName);
1669 1 : pszSchemaName = CPLStrdup("pg_temp");
1670 : }
1671 233 : if (pszSchemaName == nullptr)
1672 : {
1673 212 : pszSchemaName = CPLStrdup(osCurrentSchema);
1674 : }
1675 :
1676 : // Check that the schema exist. If there is a single match in a case
1677 : // insensitive way, use it. Otherwise error out if the match is not exact.
1678 : {
1679 233 : const auto osNewSchemaName = FindSchema(pszSchemaName);
1680 233 : if (!osNewSchemaName.has_value())
1681 : {
1682 4 : CPLFree(pszTableName);
1683 4 : CPLFree(pszSchemaName);
1684 4 : return nullptr;
1685 : }
1686 229 : CPLFree(pszSchemaName);
1687 229 : pszSchemaName = CPLStrdup(osNewSchemaName->c_str());
1688 : }
1689 :
1690 : /* -------------------------------------------------------------------- */
1691 : /* Do we already have this layer? If so, should we blow it */
1692 : /* away? */
1693 : /* -------------------------------------------------------------------- */
1694 458 : CPLString osSQLLayerName;
1695 458 : if (pszSchemaName == nullptr ||
1696 229 : (!osCurrentSchema.empty() &&
1697 229 : EQUAL(pszSchemaName, osCurrentSchema.c_str())))
1698 212 : osSQLLayerName = pszTableName;
1699 : else
1700 : {
1701 17 : osSQLLayerName = pszSchemaName;
1702 17 : osSQLLayerName += ".";
1703 17 : osSQLLayerName += pszTableName;
1704 : }
1705 :
1706 : /* GetLayerByName() can instantiate layers that would have been */
1707 : /* 'hidden' otherwise, for example, non-spatial tables in a */
1708 : /* PostGIS-enabled database, so this apparently useless command is */
1709 : /* not useless. (#4012) */
1710 229 : CPLPushErrorHandler(CPLQuietErrorHandler);
1711 229 : GetLayerByName(osSQLLayerName);
1712 229 : CPLPopErrorHandler();
1713 229 : CPLErrorReset();
1714 :
1715 : /* Force loading of all registered tables */
1716 229 : GetLayerCount();
1717 :
1718 317 : for (int iLayer = 0; iLayer < nLayers; iLayer++)
1719 : {
1720 90 : if (EQUAL(osSQLLayerName.c_str(), papoLayers[iLayer]->GetName()))
1721 : {
1722 30 : if (CSLFetchNameValue(papszOptions, "OVERWRITE") != nullptr &&
1723 14 : !EQUAL(CSLFetchNameValue(papszOptions, "OVERWRITE"), "NO"))
1724 : {
1725 14 : DeleteLayer(iLayer);
1726 : }
1727 : else
1728 : {
1729 2 : CPLError(CE_Failure, CPLE_AppDefined,
1730 : "Layer %s already exists, CreateLayer failed.\n"
1731 : "Use the layer creation option OVERWRITE=YES to "
1732 : "replace it.",
1733 : osSQLLayerName.c_str());
1734 2 : CPLFree(pszTableName);
1735 2 : CPLFree(pszSchemaName);
1736 2 : return nullptr;
1737 : }
1738 : }
1739 : }
1740 :
1741 : /* -------------------------------------------------------------------- */
1742 : /* Handle the GEOM_TYPE option. */
1743 : /* -------------------------------------------------------------------- */
1744 227 : pszGeomType = CSLFetchNameValue(papszOptions, "GEOM_TYPE");
1745 227 : if (pszGeomType == nullptr)
1746 : {
1747 221 : if (bHavePostGIS)
1748 130 : pszGeomType = "geometry";
1749 : else
1750 91 : pszGeomType = "bytea";
1751 : }
1752 :
1753 227 : const char *pszGFldName = CSLFetchNameValue(papszOptions, "GEOMETRY_NAME");
1754 227 : if (eType != wkbNone && EQUAL(pszGeomType, "geography"))
1755 : {
1756 5 : if (!bHaveGeography)
1757 : {
1758 0 : CPLError(
1759 : CE_Failure, CPLE_AppDefined,
1760 : "GEOM_TYPE=geography is only supported in PostGIS >= 1.5.\n"
1761 : "Creation of layer %s has failed.",
1762 : pszLayerName);
1763 0 : CPLFree(pszTableName);
1764 0 : CPLFree(pszSchemaName);
1765 0 : return nullptr;
1766 : }
1767 :
1768 5 : if (pszGFldName == nullptr)
1769 2 : pszGFldName = "the_geog";
1770 : }
1771 222 : else if (eType != wkbNone && bHavePostGIS &&
1772 110 : !EQUAL(pszGeomType, "geography"))
1773 : {
1774 110 : if (pszGFldName == nullptr)
1775 108 : pszGFldName = "wkb_geometry";
1776 : }
1777 :
1778 227 : if (eType != wkbNone && bHavePostGIS && !EQUAL(pszGeomType, "geometry") &&
1779 5 : !EQUAL(pszGeomType, "geography"))
1780 : {
1781 0 : if (bHaveGeography)
1782 0 : CPLError(CE_Failure, CPLE_AppDefined,
1783 : "GEOM_TYPE in PostGIS enabled databases must be "
1784 : "'geometry' or 'geography'.\n"
1785 : "Creation of layer %s with GEOM_TYPE %s has failed.",
1786 : pszLayerName, pszGeomType);
1787 : else
1788 0 : CPLError(
1789 : CE_Failure, CPLE_AppDefined,
1790 : "GEOM_TYPE in PostGIS enabled databases must be 'geometry'.\n"
1791 : "Creation of layer %s with GEOM_TYPE %s has failed.",
1792 : pszLayerName, pszGeomType);
1793 :
1794 0 : CPLFree(pszTableName);
1795 0 : CPLFree(pszSchemaName);
1796 0 : return nullptr;
1797 : }
1798 :
1799 : /* -------------------------------------------------------------------- */
1800 : /* Try to get the SRS Id of this spatial reference system, */
1801 : /* adding tot the srs table if needed. */
1802 : /* -------------------------------------------------------------------- */
1803 227 : int nSRSId = nUndefinedSRID;
1804 :
1805 227 : if (poSRS != nullptr)
1806 12 : nSRSId = FetchSRSId(poSRS);
1807 :
1808 227 : if (eType != wkbNone && EQUAL(pszGeomType, "geography"))
1809 : {
1810 5 : if (poSRS != nullptr && !poSRS->IsGeographic())
1811 : {
1812 1 : CPLError(CE_Failure, CPLE_NotSupported,
1813 : "geography type only supports geographic SRS");
1814 :
1815 1 : CPLFree(pszTableName);
1816 1 : CPLFree(pszSchemaName);
1817 1 : return nullptr;
1818 : }
1819 :
1820 4 : if (!(sPostGISVersion.nMajor >= 3 ||
1821 0 : (sPostGISVersion.nMajor == 2 && sPostGISVersion.nMinor >= 2)) &&
1822 0 : nSRSId != nUndefinedSRID && nSRSId != 4326)
1823 : {
1824 0 : CPLError(CE_Failure, CPLE_NotSupported,
1825 : "geography type in PostGIS < 2.2 only supports SRS = "
1826 : "EPSG:4326");
1827 :
1828 0 : CPLFree(pszTableName);
1829 0 : CPLFree(pszSchemaName);
1830 0 : return nullptr;
1831 : }
1832 :
1833 4 : if (nSRSId == nUndefinedSRID)
1834 : {
1835 3 : CPLError(CE_Warning, CPLE_AppDefined,
1836 : "Assuming EPSG:4326 for geographic type (but no implicit "
1837 : "reprojection to it will be done)");
1838 3 : nSRSId = 4326;
1839 : }
1840 : }
1841 :
1842 226 : const char *pszGeometryType = OGRToOGCGeomType(eType);
1843 :
1844 : int bDeferredCreation =
1845 226 : CPLTestBool(CPLGetConfigOption("OGR_PG_DEFERRED_CREATION", "YES"));
1846 226 : if (!bHavePostGIS)
1847 91 : bDeferredCreation =
1848 : FALSE; /* to avoid unnecessary implementation and testing burden */
1849 :
1850 : /* -------------------------------------------------------------------- */
1851 : /* Create a basic table with the FID. Also include the */
1852 : /* geometry if this is not a PostGIS enabled table. */
1853 : /* -------------------------------------------------------------------- */
1854 226 : const bool bFID64 = CPLFetchBool(papszOptions, "FID64", false);
1855 226 : const char *pszSerialType = bFID64 ? "BIGSERIAL" : "SERIAL";
1856 :
1857 452 : CPLString osCreateTable;
1858 226 : if (bTemporary)
1859 : {
1860 : osCreateTable.Printf("CREATE TEMPORARY TABLE %s",
1861 1 : OGRPGEscapeColumnName(pszTableName).c_str());
1862 : }
1863 : else
1864 : {
1865 : osCreateTable.Printf(
1866 : "CREATE%s TABLE %s.%s",
1867 225 : CPLFetchBool(papszOptions, "UNLOGGED", false) ? " UNLOGGED" : "",
1868 450 : OGRPGEscapeColumnName(pszSchemaName).c_str(),
1869 900 : OGRPGEscapeColumnName(pszTableName).c_str());
1870 : }
1871 :
1872 226 : const char *suffix = nullptr;
1873 226 : if ((GeometryTypeFlags & OGRGeometry::OGR_G_3D) &&
1874 77 : (GeometryTypeFlags & OGRGeometry::OGR_G_MEASURED))
1875 4 : suffix = "ZM";
1876 226 : else if ((GeometryTypeFlags & OGRGeometry::OGR_G_MEASURED) &&
1877 4 : (EQUAL(pszGeomType, "geography") ||
1878 3 : wkbFlatten(eType) != wkbUnknown))
1879 2 : suffix = "M";
1880 220 : else if (GeometryTypeFlags & OGRGeometry::OGR_G_3D)
1881 73 : suffix = "Z";
1882 : else
1883 147 : suffix = "";
1884 :
1885 : {
1886 452 : CPLString osCommand;
1887 226 : if (eType != wkbNone && !bHavePostGIS)
1888 : {
1889 83 : if (pszGFldName && !EQUAL(pszGFldName, "wkb_geometry"))
1890 : {
1891 3 : CPLError(CE_Warning, CPLE_AppDefined,
1892 : "GEOMETRY_NAME=%s ignored, and set instead to "
1893 : "'wkb_geometry' as it is the only geometry column "
1894 : "name recognized for non-PostGIS enabled databases.",
1895 : pszGFldName);
1896 : }
1897 83 : pszGFldName = "wkb_geometry";
1898 83 : osCommand.Printf("%s ( "
1899 : " %s %s, "
1900 : " %s %s, "
1901 : " PRIMARY KEY (%s)",
1902 : osCreateTable.c_str(),
1903 : osFIDColumnNameEscaped.c_str(), pszSerialType,
1904 : pszGFldName, pszGeomType,
1905 83 : osFIDColumnNameEscaped.c_str());
1906 : }
1907 143 : else if (!bDeferredCreation && eType != wkbNone &&
1908 2 : EQUAL(pszGeomType, "geography"))
1909 : {
1910 1 : osCommand.Printf(
1911 : "%s ( %s %s, %s geography(%s%s%s), PRIMARY KEY (%s)",
1912 : osCreateTable.c_str(), osFIDColumnNameEscaped.c_str(),
1913 2 : pszSerialType, OGRPGEscapeColumnName(pszGFldName).c_str(),
1914 : pszGeometryType, suffix,
1915 1 : nSRSId ? CPLSPrintf(",%d", nSRSId) : "",
1916 2 : osFIDColumnNameEscaped.c_str());
1917 : }
1918 142 : else if (!bDeferredCreation && eType != wkbNone &&
1919 1 : !EQUAL(pszGeomType, "geography") &&
1920 1 : sPostGISVersion.nMajor >= 2)
1921 : {
1922 1 : osCommand.Printf(
1923 : "%s ( %s %s, %s geometry(%s%s%s), PRIMARY KEY (%s)",
1924 : osCreateTable.c_str(), osFIDColumnNameEscaped.c_str(),
1925 2 : pszSerialType, OGRPGEscapeColumnName(pszGFldName).c_str(),
1926 : pszGeometryType, suffix,
1927 0 : nSRSId ? CPLSPrintf(",%d", nSRSId) : "",
1928 2 : osFIDColumnNameEscaped.c_str());
1929 : }
1930 : else
1931 : {
1932 : osCommand.Printf("%s ( %s %s, PRIMARY KEY (%s)",
1933 : osCreateTable.c_str(),
1934 : osFIDColumnNameEscaped.c_str(), pszSerialType,
1935 141 : osFIDColumnNameEscaped.c_str());
1936 : }
1937 226 : osCreateTable = std::move(osCommand);
1938 : }
1939 :
1940 : const char *pszSI =
1941 226 : CSLFetchNameValueDef(papszOptions, "SPATIAL_INDEX", "GIST");
1942 226 : bool bCreateSpatialIndex =
1943 0 : (EQUAL(pszSI, "GIST") || EQUAL(pszSI, "SPGIST") ||
1944 226 : EQUAL(pszSI, "BRIN") || EQUAL(pszSI, "YES") || EQUAL(pszSI, "ON") ||
1945 0 : EQUAL(pszSI, "TRUE"));
1946 226 : if (!bCreateSpatialIndex && !EQUAL(pszSI, "NO") && !EQUAL(pszSI, "OFF") &&
1947 0 : !EQUAL(pszSI, "FALSE") && !EQUAL(pszSI, "NONE"))
1948 : {
1949 0 : CPLError(CE_Warning, CPLE_NotSupported,
1950 : "SPATIAL_INDEX=%s not supported", pszSI);
1951 : }
1952 452 : const char *pszSpatialIndexType = EQUAL(pszSI, "SPGIST") ? "SPGIST"
1953 226 : : EQUAL(pszSI, "BRIN") ? "BRIN"
1954 : : "GIST";
1955 197 : if (eType != wkbNone && bCreateSpatialIndex &&
1956 423 : CPLFetchBool(papszOptions, "UNLOGGED", false) &&
1957 0 : !(sPostgreSQLVersion.nMajor > 9 ||
1958 0 : (sPostgreSQLVersion.nMajor == 9 && sPostgreSQLVersion.nMinor >= 3)))
1959 : {
1960 0 : CPLError(
1961 : CE_Warning, CPLE_NotSupported,
1962 : "GiST index only supported since Postgres 9.3 on unlogged table");
1963 0 : bCreateSpatialIndex = false;
1964 : }
1965 :
1966 226 : if (!bDeferredCreation)
1967 : {
1968 93 : SoftStartTransaction();
1969 :
1970 93 : CPLString osCommand = osCreateTable;
1971 93 : osCommand += " )";
1972 :
1973 93 : PGresult *hResult = OGRPG_PQexec(hPGConn, osCommand.c_str());
1974 93 : if (PQresultStatus(hResult) != PGRES_COMMAND_OK)
1975 : {
1976 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s\n%s", osCommand.c_str(),
1977 0 : PQerrorMessage(hPGConn));
1978 0 : CPLFree(pszTableName);
1979 0 : CPLFree(pszSchemaName);
1980 :
1981 0 : OGRPGClearResult(hResult);
1982 :
1983 0 : SoftRollbackTransaction();
1984 0 : return nullptr;
1985 : }
1986 :
1987 93 : OGRPGClearResult(hResult);
1988 :
1989 93 : if (eType != wkbNone && bHavePostGIS && bCreateSpatialIndex)
1990 : {
1991 : // Create the spatial index.
1992 : // We're doing this before we add geometry and record to the
1993 : // table, so this may not be exactly the best way to do it.
1994 : const std::string osIndexName(OGRPGCommonGenerateSpatialIndexName(
1995 2 : pszTableName, pszGFldName, 0));
1996 :
1997 : osCommand.Printf("CREATE INDEX %s ON %s.%s USING %s (%s)",
1998 4 : OGRPGEscapeColumnName(osIndexName.c_str()).c_str(),
1999 4 : OGRPGEscapeColumnName(pszSchemaName).c_str(),
2000 4 : OGRPGEscapeColumnName(pszTableName).c_str(),
2001 : pszSpatialIndexType,
2002 10 : OGRPGEscapeColumnName(pszGFldName).c_str());
2003 :
2004 2 : hResult = OGRPG_PQexec(hPGConn, osCommand.c_str());
2005 :
2006 2 : if (!hResult || PQresultStatus(hResult) != PGRES_COMMAND_OK)
2007 : {
2008 0 : CPLError(CE_Failure, CPLE_AppDefined,
2009 : "'%s' failed for layer %s, index creation has failed.",
2010 : osCommand.c_str(), pszLayerName);
2011 :
2012 0 : CPLFree(pszTableName);
2013 0 : CPLFree(pszSchemaName);
2014 :
2015 0 : OGRPGClearResult(hResult);
2016 :
2017 0 : SoftRollbackTransaction();
2018 :
2019 0 : return nullptr;
2020 : }
2021 2 : OGRPGClearResult(hResult);
2022 : }
2023 :
2024 : /* --------------------------------------------------------------------
2025 : */
2026 : /* Complete, and commit the transaction. */
2027 : /* --------------------------------------------------------------------
2028 : */
2029 93 : SoftCommitTransaction();
2030 : }
2031 :
2032 : /* -------------------------------------------------------------------- */
2033 : /* Create the layer object. */
2034 : /* -------------------------------------------------------------------- */
2035 : OGRPGTableLayer *poLayer = new OGRPGTableLayer(
2036 226 : this, osCurrentSchema, pszTableName, pszSchemaName, "", nullptr, TRUE);
2037 226 : poLayer->SetTableDefinition(osFIDColumnName, pszGFldName, eType,
2038 : pszGeomType, nSRSId, GeometryTypeFlags);
2039 226 : poLayer->SetLaunderFlag(bLaunder);
2040 226 : poLayer->SetUTF8ToASCIIFlag(bUTF8ToASCII);
2041 226 : poLayer->SetPrecisionFlag(CPLFetchBool(papszOptions, "PRECISION", true));
2042 : // poLayer->SetForcedSRSId(nForcedSRSId);
2043 226 : poLayer->SetForcedGeometryTypeFlags(ForcedGeometryTypeFlags);
2044 226 : poLayer->SetCreateSpatialIndex(bCreateSpatialIndex, pszSpatialIndexType);
2045 226 : poLayer->SetDeferredCreation(bDeferredCreation, osCreateTable);
2046 :
2047 226 : const char *pszDescription = CSLFetchNameValue(papszOptions, "DESCRIPTION");
2048 226 : if (pszDescription != nullptr)
2049 2 : poLayer->SetForcedDescription(pszDescription);
2050 :
2051 : /* HSTORE_COLUMNS existed at a time during GDAL 1.10dev */
2052 : const char *pszHSTOREColumns =
2053 226 : CSLFetchNameValue(papszOptions, "HSTORE_COLUMNS");
2054 226 : if (pszHSTOREColumns != nullptr)
2055 0 : CPLError(CE_Warning, CPLE_AppDefined,
2056 : "HSTORE_COLUMNS not recognized. Use COLUMN_TYPES instead.");
2057 :
2058 : const char *pszOverrideColumnTypes =
2059 226 : CSLFetchNameValue(papszOptions, "COLUMN_TYPES");
2060 226 : poLayer->SetOverrideColumnTypes(pszOverrideColumnTypes);
2061 :
2062 226 : poLayer->AllowAutoFIDOnCreateViaCopy();
2063 226 : if (CPLTestBool(CPLGetConfigOption("PG_USE_COPY", "YES")))
2064 220 : poLayer->SetUseCopy();
2065 :
2066 226 : if (bFID64)
2067 2 : poLayer->SetMetadataItem(OLMD_FID64, "YES");
2068 :
2069 : /* -------------------------------------------------------------------- */
2070 : /* Add layer to data source layer list. */
2071 : /* -------------------------------------------------------------------- */
2072 226 : papoLayers = static_cast<OGRPGTableLayer **>(
2073 226 : CPLRealloc(papoLayers, sizeof(OGRPGTableLayer *) * (nLayers + 1)));
2074 :
2075 226 : papoLayers[nLayers++] = poLayer;
2076 :
2077 226 : CPLFree(pszTableName);
2078 226 : CPLFree(pszSchemaName);
2079 :
2080 226 : return poLayer;
2081 : }
2082 :
2083 : /************************************************************************/
2084 : /* TestCapability() */
2085 : /************************************************************************/
2086 :
2087 115 : int OGRPGDataSource::TestCapability(const char *pszCap) const
2088 :
2089 : {
2090 115 : if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
2091 84 : EQUAL(pszCap, ODsCCreateGeomFieldAfterCreateLayer))
2092 51 : return TRUE;
2093 64 : else if (EQUAL(pszCap, ODsCCurveGeometries))
2094 12 : return TRUE;
2095 52 : else if (EQUAL(pszCap, ODsCTransactions))
2096 20 : return TRUE;
2097 32 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
2098 12 : return TRUE;
2099 20 : else if (EQUAL(pszCap, ODsCZGeometries))
2100 14 : return TRUE;
2101 6 : else if (EQUAL(pszCap, ODsCRandomLayerWrite))
2102 0 : return TRUE;
2103 : else
2104 6 : return FALSE;
2105 : }
2106 :
2107 : /************************************************************************/
2108 : /* GetLayerCount() */
2109 : /************************************************************************/
2110 :
2111 1290 : int OGRPGDataSource::GetLayerCount() const
2112 : {
2113 1290 : const_cast<OGRPGDataSource *>(this)->LoadTables();
2114 1290 : return nLayers;
2115 : }
2116 :
2117 : /************************************************************************/
2118 : /* GetLayer() */
2119 : /************************************************************************/
2120 :
2121 324 : const OGRLayer *OGRPGDataSource::GetLayer(int iLayer) const
2122 :
2123 : {
2124 : /* Force loading of all registered tables */
2125 324 : if (iLayer < 0 || iLayer >= GetLayerCount())
2126 10 : return nullptr;
2127 : else
2128 314 : return papoLayers[iLayer];
2129 : }
2130 :
2131 : /************************************************************************/
2132 : /* FindSchema() */
2133 : /************************************************************************/
2134 :
2135 : // Check that the schema exists. If there is a single match in a case
2136 : // insensitive way, use it. Otherwise error out if the match is not exact.
2137 : // Return the schema name with its exact case from pg_catalog, or an empty
2138 : // string if an error occurs.
2139 : std::optional<std::string>
2140 289 : OGRPGDataSource::FindSchema(const char *pszSchemaNameIn)
2141 : {
2142 289 : if (strcmp(pszSchemaNameIn, "public") == 0 ||
2143 286 : strcmp(pszSchemaNameIn, "pg_temp") == 0)
2144 : {
2145 5 : return pszSchemaNameIn;
2146 : }
2147 :
2148 284 : EndCopy();
2149 :
2150 568 : std::string osSchemaName;
2151 : std::string osCommand(
2152 568 : "SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname ILIKE ");
2153 284 : osCommand += OGRPGEscapeString(hPGConn, pszSchemaNameIn);
2154 284 : PGresult *hResult = OGRPG_PQexec(hPGConn, osCommand.c_str());
2155 284 : if (hResult && PQntuples(hResult) == 1)
2156 : {
2157 276 : osSchemaName = PQgetvalue(hResult, 0, 0);
2158 : }
2159 8 : else if (hResult)
2160 : {
2161 8 : const int nTuples = PQntuples(hResult);
2162 8 : if (nTuples == 0)
2163 : {
2164 2 : CPLError(CE_Failure, CPLE_AppDefined,
2165 : "Schema \"%s\" does not exist.", pszSchemaNameIn);
2166 2 : return {};
2167 : }
2168 14 : for (int i = 0; i < nTuples; ++i)
2169 : {
2170 12 : if (strcmp(PQgetvalue(hResult, i, 0), pszSchemaNameIn) == 0)
2171 : {
2172 4 : osSchemaName = pszSchemaNameIn;
2173 4 : break;
2174 : }
2175 : }
2176 6 : if (osSchemaName.empty())
2177 : {
2178 2 : CPLError(CE_Failure, CPLE_AppDefined,
2179 : "Several schemas exist whose name matches \"%s\", but "
2180 : "not with that case. "
2181 : "Please specify the schema name with the exact case.",
2182 : pszSchemaNameIn);
2183 2 : return {};
2184 : }
2185 : }
2186 280 : OGRPGClearResult(hResult);
2187 :
2188 280 : return osSchemaName;
2189 : }
2190 :
2191 : /************************************************************************/
2192 : /* GetLayerByName() */
2193 : /************************************************************************/
2194 :
2195 536 : OGRLayer *OGRPGDataSource::GetLayerByName(const char *pszNameIn)
2196 :
2197 : {
2198 536 : char *pszTableName = nullptr;
2199 536 : char *pszGeomColumnName = nullptr;
2200 536 : char *pszSchemaName = nullptr;
2201 :
2202 536 : if (!pszNameIn)
2203 0 : return nullptr;
2204 :
2205 : /* first a case sensitive check */
2206 : /* do NOT force loading of all registered tables */
2207 756 : for (int i = 0; i < nLayers; i++)
2208 : {
2209 316 : OGRPGTableLayer *poLayer = papoLayers[i];
2210 :
2211 316 : if (strcmp(pszNameIn, poLayer->GetName()) == 0)
2212 : {
2213 96 : return poLayer;
2214 : }
2215 : }
2216 :
2217 : /* then case insensitive */
2218 610 : for (int i = 0; i < nLayers; i++)
2219 : {
2220 170 : OGRPGTableLayer *poLayer = papoLayers[i];
2221 :
2222 170 : if (EQUAL(pszNameIn, poLayer->GetName()))
2223 : {
2224 0 : return poLayer;
2225 : }
2226 : }
2227 :
2228 440 : char *pszNameWithoutBracket = CPLStrdup(pszNameIn);
2229 440 : char *pos = strchr(pszNameWithoutBracket, '(');
2230 440 : if (pos != nullptr)
2231 : {
2232 7 : *pos = '\0';
2233 7 : pszGeomColumnName = CPLStrdup(pos + 1);
2234 7 : int len = static_cast<int>(strlen(pszGeomColumnName));
2235 7 : if (len > 0)
2236 7 : pszGeomColumnName[len - 1] = '\0';
2237 : }
2238 :
2239 440 : pos = strchr(pszNameWithoutBracket, '.');
2240 440 : if (pos != nullptr)
2241 : {
2242 56 : *pos = '\0';
2243 56 : const auto osSchemaName = FindSchema(pszNameWithoutBracket);
2244 56 : if (!osSchemaName.has_value())
2245 : {
2246 0 : CPLFree(pszNameWithoutBracket);
2247 0 : CPLFree(pszGeomColumnName);
2248 0 : return nullptr;
2249 : }
2250 56 : pszSchemaName = CPLStrdup(osSchemaName->c_str());
2251 56 : pszTableName = CPLStrdup(pos + 1);
2252 : }
2253 : else
2254 : {
2255 384 : pszTableName = CPLStrdup(pszNameWithoutBracket);
2256 : }
2257 :
2258 440 : if (strlen(pszTableName) > OGR_PG_NAMEDATALEN - 1)
2259 0 : pszTableName[OGR_PG_NAMEDATALEN - 1] = 0;
2260 :
2261 440 : CPLFree(pszNameWithoutBracket);
2262 440 : pszNameWithoutBracket = nullptr;
2263 :
2264 440 : OGRPGTableLayer *poLayer = nullptr;
2265 :
2266 440 : if (pszSchemaName != nullptr && osCurrentSchema == pszSchemaName &&
2267 : pszGeomColumnName == nullptr)
2268 : {
2269 : poLayer =
2270 26 : cpl::down_cast<OGRPGTableLayer *>(GetLayerByName(pszTableName));
2271 : }
2272 : else
2273 : {
2274 414 : EndCopy();
2275 :
2276 828 : const CPLString osTableName(pszTableName);
2277 828 : const CPLString osTableNameLower = CPLString(pszTableName).tolower();
2278 414 : if (osTableName != osTableNameLower)
2279 4 : CPLPushErrorHandler(CPLQuietErrorHandler);
2280 414 : poLayer = OpenTable(osCurrentSchema, pszTableName, pszSchemaName,
2281 : nullptr, pszGeomColumnName, bDSUpdate, TRUE);
2282 414 : if (osTableName != osTableNameLower)
2283 4 : CPLPopErrorHandler();
2284 414 : if (poLayer == nullptr && osTableName != osTableNameLower)
2285 : {
2286 : poLayer =
2287 4 : OpenTable(osCurrentSchema, osTableNameLower, pszSchemaName,
2288 : nullptr, pszGeomColumnName, bDSUpdate, TRUE);
2289 : }
2290 : }
2291 :
2292 440 : CPLFree(pszTableName);
2293 440 : CPLFree(pszSchemaName);
2294 440 : CPLFree(pszGeomColumnName);
2295 :
2296 440 : return poLayer;
2297 : }
2298 :
2299 : /************************************************************************/
2300 : /* OGRPGNoticeProcessor() */
2301 : /************************************************************************/
2302 :
2303 431 : static void OGRPGNoticeProcessor(CPL_UNUSED void *arg, const char *pszMessage)
2304 : {
2305 431 : CPLDebug("OGR_PG_NOTICE", "%s", pszMessage);
2306 431 : }
2307 :
2308 : /************************************************************************/
2309 : /* InitializeMetadataTables() */
2310 : /* */
2311 : /* Create the metadata tables (SPATIAL_REF_SYS and */
2312 : /* GEOMETRY_COLUMNS). */
2313 : /************************************************************************/
2314 :
2315 0 : OGRErr OGRPGDataSource::InitializeMetadataTables()
2316 :
2317 : {
2318 : // implement later.
2319 0 : return OGRERR_FAILURE;
2320 : }
2321 :
2322 : /************************************************************************/
2323 : /* FetchSRS() */
2324 : /* */
2325 : /* Return a SRS corresponding to a particular id. Note that */
2326 : /* reference counting should be honoured on the returned */
2327 : /* OGRSpatialReference, as handles may be cached. */
2328 : /************************************************************************/
2329 :
2330 33 : const OGRSpatialReference *OGRPGDataSource::FetchSRS(int nId)
2331 :
2332 : {
2333 33 : if (nId < 0 || !m_bHasSpatialRefSys)
2334 0 : return nullptr;
2335 :
2336 : /* -------------------------------------------------------------------- */
2337 : /* First, we look through our SRID cache, is it there? */
2338 : /* -------------------------------------------------------------------- */
2339 33 : auto oIter = m_oSRSCache.find(nId);
2340 33 : if (oIter != m_oSRSCache.end())
2341 : {
2342 6 : return oIter->second.get();
2343 : }
2344 :
2345 27 : EndCopy();
2346 :
2347 : /* -------------------------------------------------------------------- */
2348 : /* Try looking up in spatial_ref_sys table. */
2349 : /* -------------------------------------------------------------------- */
2350 54 : CPLString osCommand;
2351 27 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSRS;
2352 :
2353 27 : osCommand.Printf("SELECT srtext, auth_name, auth_srid FROM spatial_ref_sys "
2354 : "WHERE srid = %d",
2355 27 : nId);
2356 27 : PGresult *hResult = OGRPG_PQexec(hPGConn, osCommand.c_str());
2357 :
2358 54 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK &&
2359 27 : PQntuples(hResult) == 1)
2360 : {
2361 27 : const char *pszWKT = PQgetvalue(hResult, 0, 0);
2362 27 : const char *pszAuthName = PQgetvalue(hResult, 0, 1);
2363 27 : const char *pszAuthSRID = PQgetvalue(hResult, 0, 2);
2364 27 : poSRS.reset(new OGRSpatialReference());
2365 27 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2366 :
2367 : // Try to import first from EPSG code, and then from WKT
2368 27 : if (pszAuthName && pszAuthSRID && EQUAL(pszAuthName, "EPSG") &&
2369 78 : atoi(pszAuthSRID) == nId &&
2370 24 : poSRS->importFromEPSG(nId) == OGRERR_NONE)
2371 : {
2372 : // do nothing
2373 : }
2374 3 : else if (poSRS->importFromWkt(pszWKT) != OGRERR_NONE)
2375 : {
2376 0 : poSRS.reset();
2377 : }
2378 : }
2379 : else
2380 : {
2381 0 : CPLError(CE_Failure, CPLE_AppDefined, "Could not fetch SRS: %s",
2382 0 : PQerrorMessage(hPGConn));
2383 : }
2384 :
2385 27 : OGRPGClearResult(hResult);
2386 :
2387 27 : if (poSRS)
2388 27 : poSRS->StripTOWGS84IfKnownDatumAndAllowed();
2389 :
2390 : /* -------------------------------------------------------------------- */
2391 : /* Add to the cache. */
2392 : /* -------------------------------------------------------------------- */
2393 27 : oIter = m_oSRSCache.emplace(nId, std::move(poSRS)).first;
2394 27 : return oIter->second.get();
2395 : }
2396 :
2397 : /************************************************************************/
2398 : /* FetchSRSId() */
2399 : /* */
2400 : /* Fetch the id corresponding to an SRS, and if not found, add */
2401 : /* it to the table. */
2402 : /************************************************************************/
2403 :
2404 18 : int OGRPGDataSource::FetchSRSId(const OGRSpatialReference *poSRS)
2405 :
2406 : {
2407 18 : if (poSRS == nullptr || !m_bHasSpatialRefSys)
2408 0 : return nUndefinedSRID;
2409 :
2410 36 : OGRSpatialReference oSRS(*poSRS);
2411 : // cppcheck-suppress uselessAssignmentPtrArg
2412 18 : poSRS = nullptr;
2413 :
2414 18 : const char *pszAuthorityName = oSRS.GetAuthorityName(nullptr);
2415 :
2416 18 : if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0)
2417 : {
2418 : /* --------------------------------------------------------------------
2419 : */
2420 : /* Try to identify an EPSG code */
2421 : /* --------------------------------------------------------------------
2422 : */
2423 2 : oSRS.AutoIdentifyEPSG();
2424 :
2425 2 : pszAuthorityName = oSRS.GetAuthorityName(nullptr);
2426 2 : if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG"))
2427 : {
2428 1 : const char *pszAuthorityCode = oSRS.GetAuthorityCode(nullptr);
2429 1 : if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0)
2430 : {
2431 : /* Import 'clean' SRS */
2432 1 : oSRS.importFromEPSG(atoi(pszAuthorityCode));
2433 :
2434 1 : pszAuthorityName = oSRS.GetAuthorityName(nullptr);
2435 : }
2436 : }
2437 : }
2438 : /* -------------------------------------------------------------------- */
2439 : /* Check whether the authority name/code is already mapped to a */
2440 : /* SRS ID. */
2441 : /* -------------------------------------------------------------------- */
2442 36 : CPLString osCommand;
2443 18 : int nAuthorityCode = 0;
2444 18 : if (pszAuthorityName != nullptr)
2445 : {
2446 : /* Check that the authority code is integral */
2447 17 : nAuthorityCode = atoi(oSRS.GetAuthorityCode(nullptr));
2448 17 : if (nAuthorityCode > 0)
2449 : {
2450 : osCommand.Printf("SELECT srid FROM spatial_ref_sys WHERE "
2451 : "auth_name = '%s' AND auth_srid = %d",
2452 17 : pszAuthorityName, nAuthorityCode);
2453 17 : PGresult *hResult = OGRPG_PQexec(hPGConn, osCommand.c_str());
2454 :
2455 34 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK &&
2456 17 : PQntuples(hResult) > 0)
2457 : {
2458 14 : int nSRSId = atoi(PQgetvalue(hResult, 0, 0));
2459 :
2460 14 : OGRPGClearResult(hResult);
2461 :
2462 14 : return nSRSId;
2463 : }
2464 :
2465 3 : OGRPGClearResult(hResult);
2466 : }
2467 : }
2468 :
2469 : /* -------------------------------------------------------------------- */
2470 : /* Translate SRS to WKT. */
2471 : /* -------------------------------------------------------------------- */
2472 4 : char *pszWKT = nullptr;
2473 4 : if (oSRS.exportToWkt(&pszWKT) != OGRERR_NONE)
2474 : {
2475 0 : CPLFree(pszWKT);
2476 0 : return nUndefinedSRID;
2477 : }
2478 :
2479 : /* -------------------------------------------------------------------- */
2480 : /* Try to find in the existing table. */
2481 : /* -------------------------------------------------------------------- */
2482 : CPLString osWKT =
2483 8 : OGRPGEscapeString(hPGConn, pszWKT, -1, "spatial_ref_sys", "srtext");
2484 : osCommand.Printf("SELECT srid FROM spatial_ref_sys WHERE srtext = %s",
2485 4 : osWKT.c_str());
2486 4 : PGresult *hResult = OGRPG_PQexec(hPGConn, osCommand.c_str());
2487 4 : CPLFree(pszWKT); // CM: Added to prevent mem leaks
2488 4 : pszWKT = nullptr; // CM: Added
2489 :
2490 : /* -------------------------------------------------------------------- */
2491 : /* We got it! Return it. */
2492 : /* -------------------------------------------------------------------- */
2493 8 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK &&
2494 4 : PQntuples(hResult) > 0)
2495 : {
2496 0 : int nSRSId = atoi(PQgetvalue(hResult, 0, 0));
2497 :
2498 0 : OGRPGClearResult(hResult);
2499 :
2500 0 : return nSRSId;
2501 : }
2502 :
2503 : /* -------------------------------------------------------------------- */
2504 : /* If the command actually failed, then the metadata table is */
2505 : /* likely missing. Try defining it. */
2506 : /* -------------------------------------------------------------------- */
2507 : const bool bTableMissing =
2508 4 : hResult == nullptr || PQresultStatus(hResult) == PGRES_NONFATAL_ERROR;
2509 :
2510 4 : OGRPGClearResult(hResult);
2511 :
2512 4 : if (bTableMissing)
2513 : {
2514 0 : if (InitializeMetadataTables() != OGRERR_NONE)
2515 0 : return nUndefinedSRID;
2516 : }
2517 :
2518 : /* -------------------------------------------------------------------- */
2519 : /* Get the current maximum srid in the srs table. */
2520 : /* -------------------------------------------------------------------- */
2521 4 : hResult = OGRPG_PQexec(hPGConn, "SELECT MAX(srid) FROM spatial_ref_sys");
2522 :
2523 4 : int nSRSId = 1;
2524 4 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK)
2525 : {
2526 4 : nSRSId = atoi(PQgetvalue(hResult, 0, 0)) + 1;
2527 4 : OGRPGClearResult(hResult);
2528 : }
2529 :
2530 : /* -------------------------------------------------------------------- */
2531 : /* Try adding the SRS to the SRS table. */
2532 : /* -------------------------------------------------------------------- */
2533 4 : char *pszProj4 = nullptr;
2534 4 : if (oSRS.exportToProj4(&pszProj4) != OGRERR_NONE)
2535 : {
2536 0 : CPLFree(pszProj4);
2537 0 : return nUndefinedSRID;
2538 : }
2539 :
2540 4 : CPLString osProj4 = OGRPGEscapeString(hPGConn, pszProj4, -1,
2541 4 : "spatial_ref_sys", "proj4text");
2542 :
2543 4 : if (pszAuthorityName != nullptr && nAuthorityCode > 0)
2544 : {
2545 3 : nAuthorityCode = atoi(oSRS.GetAuthorityCode(nullptr));
2546 :
2547 3 : osCommand.Printf("INSERT INTO spatial_ref_sys "
2548 : "(srid,srtext,proj4text,auth_name,auth_srid) "
2549 : "VALUES (%d, %s, %s, '%s', %d)",
2550 : nSRSId, osWKT.c_str(), osProj4.c_str(),
2551 3 : pszAuthorityName, nAuthorityCode);
2552 : }
2553 : else
2554 : {
2555 : osCommand.Printf("INSERT INTO spatial_ref_sys (srid,srtext,proj4text) "
2556 : "VALUES (%d,%s,%s)",
2557 1 : nSRSId, osWKT.c_str(), osProj4.c_str());
2558 : }
2559 :
2560 : // Free everything that was allocated.
2561 4 : CPLFree(pszProj4);
2562 4 : CPLFree(pszWKT);
2563 :
2564 4 : hResult = OGRPG_PQexec(hPGConn, osCommand.c_str());
2565 4 : OGRPGClearResult(hResult);
2566 :
2567 4 : return nSRSId;
2568 : }
2569 :
2570 : /************************************************************************/
2571 : /* StartTransaction() */
2572 : /* */
2573 : /* Should only be called by user code. Not driver internals. */
2574 : /************************************************************************/
2575 :
2576 74 : OGRErr OGRPGDataSource::StartTransaction(CPL_UNUSED int bForce)
2577 : {
2578 74 : if (bUserTransactionActive)
2579 : {
2580 2 : CPLError(CE_Failure, CPLE_AppDefined,
2581 : "Transaction already established");
2582 2 : return OGRERR_FAILURE;
2583 : }
2584 :
2585 72 : CPLAssert(!bSavePointActive);
2586 72 : EndCopy();
2587 :
2588 72 : if (nSoftTransactionLevel == 0)
2589 : {
2590 66 : OGRErr eErr = DoTransactionCommand("BEGIN");
2591 66 : if (eErr != OGRERR_NONE)
2592 0 : return eErr;
2593 : }
2594 : else
2595 : {
2596 6 : OGRErr eErr = DoTransactionCommand("SAVEPOINT ogr_savepoint");
2597 6 : if (eErr != OGRERR_NONE)
2598 0 : return eErr;
2599 :
2600 6 : bSavePointActive = TRUE;
2601 : }
2602 :
2603 72 : nSoftTransactionLevel++;
2604 72 : bUserTransactionActive = true;
2605 :
2606 : /*CPLDebug("PG", "poDS=%p StartTransaction() nSoftTransactionLevel=%d",
2607 : this, nSoftTransactionLevel);*/
2608 :
2609 72 : return OGRERR_NONE;
2610 : }
2611 :
2612 : /************************************************************************/
2613 : /* CommitTransaction() */
2614 : /* */
2615 : /* Should only be called by user code. Not driver internals. */
2616 : /************************************************************************/
2617 :
2618 58 : OGRErr OGRPGDataSource::CommitTransaction()
2619 : {
2620 58 : if (!bUserTransactionActive)
2621 : {
2622 2 : CPLError(CE_Failure, CPLE_AppDefined, "Transaction not established");
2623 2 : return OGRERR_FAILURE;
2624 : }
2625 :
2626 : /*CPLDebug("PG", "poDS=%p CommitTransaction() nSoftTransactionLevel=%d",
2627 : this, nSoftTransactionLevel);*/
2628 :
2629 56 : OGRErr eErr = FlushCacheWithRet(false);
2630 56 : if (eErr != OGRERR_NONE)
2631 : {
2632 1 : RollbackTransaction();
2633 1 : return eErr;
2634 : }
2635 :
2636 55 : nSoftTransactionLevel--;
2637 55 : bUserTransactionActive = false;
2638 :
2639 55 : if (bSavePointActive)
2640 : {
2641 4 : CPLAssert(nSoftTransactionLevel > 0);
2642 4 : bSavePointActive = FALSE;
2643 :
2644 4 : eErr = DoTransactionCommand("RELEASE SAVEPOINT ogr_savepoint");
2645 : }
2646 : else
2647 : {
2648 51 : if (nSoftTransactionLevel > 0)
2649 : {
2650 : // This means we have cursors still in progress
2651 7 : for (int i = 0; i < nLayers; i++)
2652 5 : papoLayers[i]->InvalidateCursor();
2653 2 : CPLAssert(nSoftTransactionLevel == 0);
2654 : }
2655 :
2656 51 : eErr = DoTransactionCommand("COMMIT");
2657 : }
2658 :
2659 55 : return eErr;
2660 : }
2661 :
2662 : /************************************************************************/
2663 : /* RollbackTransaction() */
2664 : /* */
2665 : /* Should only be called by user code. Not driver internals. */
2666 : /************************************************************************/
2667 :
2668 19 : OGRErr OGRPGDataSource::RollbackTransaction()
2669 : {
2670 19 : if (!bUserTransactionActive)
2671 : {
2672 2 : CPLError(CE_Failure, CPLE_AppDefined, "Transaction not established");
2673 2 : return OGRERR_FAILURE;
2674 : }
2675 :
2676 : /*CPLDebug("PG", "poDS=%p RollbackTransaction() nSoftTransactionLevel=%d",
2677 : this, nSoftTransactionLevel);*/
2678 :
2679 17 : FlushCache(false);
2680 :
2681 17 : nSoftTransactionLevel--;
2682 17 : bUserTransactionActive = false;
2683 :
2684 : OGRErr eErr;
2685 17 : if (bSavePointActive)
2686 : {
2687 2 : CPLAssert(nSoftTransactionLevel > 0);
2688 2 : bSavePointActive = FALSE;
2689 :
2690 2 : eErr = DoTransactionCommand("ROLLBACK TO SAVEPOINT ogr_savepoint");
2691 : }
2692 : else
2693 : {
2694 15 : if (nSoftTransactionLevel > 0)
2695 : {
2696 : // This means we have cursors still in progress
2697 0 : for (int i = 0; i < nLayers; i++)
2698 0 : papoLayers[i]->InvalidateCursor();
2699 0 : CPLAssert(nSoftTransactionLevel == 0);
2700 : }
2701 :
2702 15 : eErr = DoTransactionCommand("ROLLBACK");
2703 : }
2704 :
2705 17 : return eErr;
2706 : }
2707 :
2708 : /************************************************************************/
2709 : /* SoftStartTransaction() */
2710 : /* */
2711 : /* Create a transaction scope. If we already have a */
2712 : /* transaction active this isn't a real transaction, but just */
2713 : /* an increment to the scope count. */
2714 : /************************************************************************/
2715 :
2716 920 : OGRErr OGRPGDataSource::SoftStartTransaction()
2717 :
2718 : {
2719 920 : nSoftTransactionLevel++;
2720 : /*CPLDebug("PG", "poDS=%p SoftStartTransaction() nSoftTransactionLevel=%d",
2721 : this, nSoftTransactionLevel);*/
2722 :
2723 920 : OGRErr eErr = OGRERR_NONE;
2724 920 : if (nSoftTransactionLevel == 1)
2725 : {
2726 835 : eErr = DoTransactionCommand("BEGIN");
2727 : }
2728 :
2729 920 : return eErr;
2730 : }
2731 :
2732 : /************************************************************************/
2733 : /* SoftCommitTransaction() */
2734 : /* */
2735 : /* Commit the current transaction if we are at the outer */
2736 : /* scope. */
2737 : /************************************************************************/
2738 :
2739 918 : OGRErr OGRPGDataSource::SoftCommitTransaction()
2740 :
2741 : {
2742 918 : EndCopy();
2743 :
2744 : /*CPLDebug("PG", "poDS=%p SoftCommitTransaction() nSoftTransactionLevel=%d",
2745 : this, nSoftTransactionLevel);*/
2746 :
2747 918 : if (nSoftTransactionLevel <= 0)
2748 : {
2749 0 : CPLAssert(false);
2750 : return OGRERR_FAILURE;
2751 : }
2752 :
2753 918 : OGRErr eErr = OGRERR_NONE;
2754 918 : nSoftTransactionLevel--;
2755 918 : if (nSoftTransactionLevel == 0)
2756 : {
2757 835 : CPLAssert(!bSavePointActive);
2758 :
2759 835 : eErr = DoTransactionCommand("COMMIT");
2760 : }
2761 :
2762 918 : return eErr;
2763 : }
2764 :
2765 : /************************************************************************/
2766 : /* SoftRollbackTransaction() */
2767 : /* */
2768 : /* Do a rollback of the current transaction if we are at the 1st */
2769 : /* level */
2770 : /************************************************************************/
2771 :
2772 2 : OGRErr OGRPGDataSource::SoftRollbackTransaction()
2773 :
2774 : {
2775 2 : EndCopy();
2776 :
2777 : /*CPLDebug("PG", "poDS=%p SoftRollbackTransaction()
2778 : nSoftTransactionLevel=%d", this, nSoftTransactionLevel);*/
2779 :
2780 2 : if (nSoftTransactionLevel <= 0)
2781 : {
2782 0 : CPLAssert(false);
2783 : return OGRERR_FAILURE;
2784 : }
2785 :
2786 2 : OGRErr eErr = OGRERR_NONE;
2787 2 : nSoftTransactionLevel--;
2788 2 : if (nSoftTransactionLevel == 0)
2789 : {
2790 2 : CPLAssert(!bSavePointActive);
2791 :
2792 2 : eErr = DoTransactionCommand("ROLLBACK");
2793 : }
2794 :
2795 2 : return eErr;
2796 : }
2797 :
2798 : /************************************************************************/
2799 : /* FlushSoftTransaction() */
2800 : /* */
2801 : /* Force the unwinding of any active transaction, and its */
2802 : /* commit. Should only be used by datasource destructor */
2803 : /************************************************************************/
2804 :
2805 421 : OGRErr OGRPGDataSource::FlushSoftTransaction()
2806 :
2807 : {
2808 : /*CPLDebug("PG", "poDS=%p FlushSoftTransaction() nSoftTransactionLevel=%d",
2809 : this, nSoftTransactionLevel);*/
2810 :
2811 421 : if (nSoftTransactionLevel <= 0)
2812 421 : return OGRERR_NONE;
2813 :
2814 0 : bSavePointActive = FALSE;
2815 :
2816 0 : CPLAssert(nSoftTransactionLevel == 1);
2817 0 : nSoftTransactionLevel = 0;
2818 0 : return DoTransactionCommand("COMMIT");
2819 : }
2820 :
2821 : /************************************************************************/
2822 : /* DoTransactionCommand() */
2823 : /************************************************************************/
2824 :
2825 1816 : OGRErr OGRPGDataSource::DoTransactionCommand(const char *pszCommand)
2826 :
2827 : {
2828 1816 : OGRErr eErr = OGRERR_NONE;
2829 1816 : PGconn *l_hPGConn = GetPGConn();
2830 :
2831 1816 : PGresult *hResult = OGRPG_PQexec(l_hPGConn, pszCommand);
2832 1816 : osDebugLastTransactionCommand = pszCommand;
2833 :
2834 1816 : if (!hResult || PQresultStatus(hResult) != PGRES_COMMAND_OK)
2835 : {
2836 0 : eErr = OGRERR_FAILURE;
2837 : }
2838 :
2839 1816 : OGRPGClearResult(hResult);
2840 :
2841 1816 : return eErr;
2842 : }
2843 :
2844 : /************************************************************************/
2845 : /* OGRPGNoResetResultLayer */
2846 : /************************************************************************/
2847 :
2848 : class OGRPGNoResetResultLayer final : public OGRPGLayer
2849 : {
2850 : public:
2851 : OGRPGNoResetResultLayer(OGRPGDataSource *poDSIn, PGresult *hResultIn);
2852 :
2853 : ~OGRPGNoResetResultLayer() override;
2854 :
2855 : void ResetReading() override;
2856 :
2857 0 : int TestCapability(const char *) const override
2858 : {
2859 0 : return FALSE;
2860 : }
2861 :
2862 : OGRFeature *GetNextFeature() override;
2863 :
2864 0 : CPLString GetFromClauseForGetExtent() override
2865 : {
2866 0 : CPLAssert(false);
2867 : return "";
2868 : }
2869 :
2870 47 : void ResolveSRID(const OGRPGGeomFieldDefn *poGFldDefn) override
2871 : {
2872 47 : poGFldDefn->nSRSId = -1;
2873 47 : }
2874 : };
2875 :
2876 : /************************************************************************/
2877 : /* OGRPGNoResetResultLayer() */
2878 : /************************************************************************/
2879 :
2880 96 : OGRPGNoResetResultLayer::OGRPGNoResetResultLayer(OGRPGDataSource *poDSIn,
2881 96 : PGresult *hResultIn)
2882 : {
2883 96 : poDS = poDSIn;
2884 96 : ReadResultDefinition(hResultIn);
2885 96 : hCursorResult = hResultIn;
2886 96 : CreateMapFromFieldNameToIndex(hCursorResult, poFeatureDefn,
2887 96 : m_panMapFieldNameToIndex,
2888 96 : m_panMapFieldNameToGeomIndex);
2889 96 : }
2890 :
2891 : /************************************************************************/
2892 : /* ~OGRPGNoResetResultLayer() */
2893 : /************************************************************************/
2894 :
2895 384 : OGRPGNoResetResultLayer::~OGRPGNoResetResultLayer()
2896 :
2897 : {
2898 96 : OGRPGClearResult(hCursorResult);
2899 96 : hCursorResult = nullptr;
2900 192 : }
2901 :
2902 : /************************************************************************/
2903 : /* ResetReading() */
2904 : /************************************************************************/
2905 :
2906 96 : void OGRPGNoResetResultLayer::ResetReading()
2907 : {
2908 96 : iNextShapeId = 0;
2909 96 : }
2910 :
2911 : /************************************************************************/
2912 : /* GetNextFeature() */
2913 : /************************************************************************/
2914 :
2915 192 : OGRFeature *OGRPGNoResetResultLayer::GetNextFeature()
2916 :
2917 : {
2918 192 : if (iNextShapeId == PQntuples(hCursorResult))
2919 : {
2920 96 : return nullptr;
2921 : }
2922 192 : return RecordToFeature(hCursorResult, m_panMapFieldNameToIndex,
2923 96 : m_panMapFieldNameToGeomIndex,
2924 96 : static_cast<int>(iNextShapeId++));
2925 : }
2926 :
2927 : /************************************************************************/
2928 : /* OGRPGMemLayerWrapper */
2929 : /************************************************************************/
2930 :
2931 : class OGRPGMemLayerWrapper final : public OGRLayer
2932 : {
2933 : private:
2934 : OGRPGMemLayerWrapper(const OGRPGMemLayerWrapper &) = delete;
2935 : OGRPGMemLayerWrapper &operator=(const OGRPGMemLayerWrapper &) = delete;
2936 :
2937 : GDALDataset *poMemDS = nullptr;
2938 : OGRLayer *poMemLayer = nullptr;
2939 :
2940 : public:
2941 96 : explicit OGRPGMemLayerWrapper(GDALDataset *poMemDSIn)
2942 96 : {
2943 96 : poMemDS = poMemDSIn;
2944 96 : poMemLayer = poMemDS->GetLayer(0);
2945 96 : }
2946 :
2947 : ~OGRPGMemLayerWrapper() override;
2948 :
2949 0 : void ResetReading() override
2950 : {
2951 0 : poMemLayer->ResetReading();
2952 0 : }
2953 :
2954 80 : OGRFeature *GetNextFeature() override
2955 : {
2956 80 : return poMemLayer->GetNextFeature();
2957 : }
2958 :
2959 0 : const OGRFeatureDefn *GetLayerDefn() const override
2960 : {
2961 0 : return poMemLayer->GetLayerDefn();
2962 : }
2963 :
2964 0 : int TestCapability(const char *) const override
2965 : {
2966 0 : return FALSE;
2967 : }
2968 : };
2969 :
2970 190 : OGRPGMemLayerWrapper::~OGRPGMemLayerWrapper()
2971 : {
2972 95 : delete poMemDS;
2973 190 : }
2974 :
2975 : /************************************************************************/
2976 : /* GetMetadataItem() */
2977 : /************************************************************************/
2978 :
2979 516 : const char *OGRPGDataSource::GetMetadataItem(const char *pszKey,
2980 : const char *pszDomain)
2981 : {
2982 : /* Only used by ogr_pg.py to check inner working */
2983 516 : if (pszDomain != nullptr && EQUAL(pszDomain, "_debug_") &&
2984 : pszKey != nullptr)
2985 : {
2986 278 : if (EQUAL(pszKey, "bHasLoadTables"))
2987 10 : return CPLSPrintf("%d", bHasLoadTables);
2988 268 : if (EQUAL(pszKey, "nSoftTransactionLevel"))
2989 70 : return CPLSPrintf("%d", nSoftTransactionLevel);
2990 198 : if (EQUAL(pszKey, "bSavePointActive"))
2991 66 : return CPLSPrintf("%d", bSavePointActive);
2992 132 : if (EQUAL(pszKey, "bUserTransactionActive"))
2993 66 : return CPLSPrintf("%d", bUserTransactionActive);
2994 66 : if (EQUAL(pszKey, "osDebugLastTransactionCommand"))
2995 : {
2996 : const char *pszRet =
2997 66 : CPLSPrintf("%s", osDebugLastTransactionCommand.c_str());
2998 66 : osDebugLastTransactionCommand = "";
2999 66 : return pszRet;
3000 : }
3001 : }
3002 238 : return GDALDataset::GetMetadataItem(pszKey, pszDomain);
3003 : }
3004 :
3005 : /************************************************************************/
3006 : /* ExecuteSQL() */
3007 : /************************************************************************/
3008 :
3009 1257 : OGRLayer *OGRPGDataSource::ExecuteSQL(const char *pszSQLCommand,
3010 : OGRGeometry *poSpatialFilter,
3011 : const char *pszDialect)
3012 :
3013 : {
3014 : /* Skip leading whitespace characters */
3015 1257 : while (std::isspace(static_cast<unsigned char>(*pszSQLCommand)))
3016 0 : pszSQLCommand++;
3017 :
3018 1257 : FlushCache(false);
3019 :
3020 : /* -------------------------------------------------------------------- */
3021 : /* Use generic implementation for recognized dialects */
3022 : /* -------------------------------------------------------------------- */
3023 1257 : if (IsGenericSQLDialect(pszDialect))
3024 0 : return GDALDataset::ExecuteSQL(pszSQLCommand, poSpatialFilter,
3025 0 : pszDialect);
3026 :
3027 : /* -------------------------------------------------------------------- */
3028 : /* Special case DELLAYER: command. */
3029 : /* -------------------------------------------------------------------- */
3030 1257 : if (STARTS_WITH_CI(pszSQLCommand, "DELLAYER:"))
3031 : {
3032 6 : const char *pszLayerName = pszSQLCommand + 9;
3033 :
3034 6 : while (*pszLayerName == ' ')
3035 0 : pszLayerName++;
3036 :
3037 6 : GetLayerCount();
3038 8 : for (int iLayer = 0; iLayer < nLayers; iLayer++)
3039 : {
3040 6 : if (EQUAL(papoLayers[iLayer]->GetName(), pszLayerName))
3041 : {
3042 4 : DeleteLayer(iLayer);
3043 4 : break;
3044 : }
3045 : }
3046 6 : return nullptr;
3047 : }
3048 :
3049 : /* -------------------------------------------------------------------- */
3050 : /* Execute the statement. */
3051 : /* -------------------------------------------------------------------- */
3052 1251 : PGresult *hResult = nullptr;
3053 :
3054 1251 : if (STARTS_WITH_CI(pszSQLCommand, "SELECT") == FALSE ||
3055 211 : (strstr(pszSQLCommand, "from") == nullptr &&
3056 198 : strstr(pszSQLCommand, "FROM") == nullptr))
3057 : {
3058 : /* For something that is not a select or a select without table, do not
3059 : */
3060 : /* run under transaction (CREATE DATABASE, VACUUM don't like
3061 : * transactions) */
3062 :
3063 1130 : hResult =
3064 1130 : OGRPG_PQexec(hPGConn, pszSQLCommand, TRUE /* multiple allowed */);
3065 1130 : if (hResult && PQresultStatus(hResult) == PGRES_TUPLES_OK)
3066 : {
3067 96 : CPLDebug("PG", "Command Results Tuples = %d", PQntuples(hResult));
3068 :
3069 : OGRPGLayer *poResultLayer =
3070 96 : new OGRPGNoResetResultLayer(this, hResult);
3071 : auto poMemDS = std::unique_ptr<GDALDataset>(
3072 96 : MEMDataset::Create("", 0, 0, 0, GDT_Unknown, nullptr));
3073 96 : poMemDS->CopyLayer(poResultLayer, "sql_statement");
3074 : OGRPGMemLayerWrapper *poResLayer =
3075 96 : new OGRPGMemLayerWrapper(poMemDS.release());
3076 96 : delete poResultLayer;
3077 96 : return poResLayer;
3078 1034 : }
3079 : }
3080 : else
3081 : {
3082 121 : SoftStartTransaction();
3083 :
3084 121 : CPLString osCommand;
3085 : osCommand.Printf("DECLARE %s CURSOR for %s", "executeSQLCursor",
3086 121 : pszSQLCommand);
3087 :
3088 121 : hResult = OGRPG_PQexec(hPGConn, osCommand);
3089 :
3090 : /* --------------------------------------------------------------------
3091 : */
3092 : /* Do we have a tuple result? If so, instantiate a results */
3093 : /* layer for it. */
3094 : /* --------------------------------------------------------------------
3095 : */
3096 121 : if (hResult && PQresultStatus(hResult) == PGRES_COMMAND_OK)
3097 : {
3098 120 : OGRPGClearResult(hResult);
3099 :
3100 120 : osCommand.Printf("FETCH 0 in %s", "executeSQLCursor");
3101 120 : hResult = OGRPG_PQexec(hPGConn, osCommand);
3102 :
3103 : OGRPGResultLayer *poLayer =
3104 120 : new OGRPGResultLayer(this, pszSQLCommand, hResult);
3105 :
3106 120 : OGRPGClearResult(hResult);
3107 :
3108 120 : osCommand.Printf("CLOSE %s", "executeSQLCursor");
3109 120 : hResult = OGRPG_PQexec(hPGConn, osCommand);
3110 120 : OGRPGClearResult(hResult);
3111 :
3112 120 : SoftCommitTransaction();
3113 :
3114 120 : if (poSpatialFilter != nullptr)
3115 5 : poLayer->SetSpatialFilter(poSpatialFilter);
3116 :
3117 120 : return poLayer;
3118 : }
3119 : else
3120 : {
3121 1 : SoftRollbackTransaction();
3122 : }
3123 : }
3124 :
3125 1035 : OGRPGClearResult(hResult);
3126 :
3127 1035 : return nullptr;
3128 : }
3129 :
3130 : /************************************************************************/
3131 : /* AbortSQL() */
3132 : /************************************************************************/
3133 :
3134 5 : OGRErr OGRPGDataSource::AbortSQL()
3135 : {
3136 5 : auto cancel = PQgetCancel(hPGConn);
3137 : int result;
3138 5 : if (cancel)
3139 : {
3140 : char errbuf[255];
3141 5 : result = PQcancel(cancel, errbuf, 255);
3142 5 : if (!result)
3143 0 : CPLDebug("PG", "Error canceling the query: %s", errbuf);
3144 5 : PQfreeCancel(cancel);
3145 5 : return result ? OGRERR_NONE : OGRERR_FAILURE;
3146 : }
3147 0 : return OGRERR_FAILURE;
3148 : }
3149 :
3150 : /************************************************************************/
3151 : /* ReleaseResultSet() */
3152 : /************************************************************************/
3153 :
3154 218 : void OGRPGDataSource::ReleaseResultSet(OGRLayer *poLayer)
3155 :
3156 : {
3157 218 : delete poLayer;
3158 218 : }
3159 :
3160 : /************************************************************************/
3161 : /* StartCopy() */
3162 : /************************************************************************/
3163 :
3164 3739 : void OGRPGDataSource::StartCopy(OGRPGTableLayer *poPGLayer)
3165 : {
3166 3739 : if (poLayerInCopyMode == poPGLayer)
3167 3554 : return;
3168 185 : EndCopy();
3169 185 : poLayerInCopyMode = poPGLayer;
3170 185 : poLayerInCopyMode->StartCopy();
3171 : }
3172 :
3173 : /************************************************************************/
3174 : /* EndCopy() */
3175 : /************************************************************************/
3176 :
3177 9690 : OGRErr OGRPGDataSource::EndCopy()
3178 : {
3179 9690 : if (poLayerInCopyMode != nullptr)
3180 : {
3181 185 : OGRErr result = poLayerInCopyMode->EndCopy();
3182 185 : poLayerInCopyMode = nullptr;
3183 :
3184 185 : return result;
3185 : }
3186 : else
3187 9505 : return OGRERR_NONE;
3188 : }
3189 :
3190 : /************************************************************************/
3191 : /* CreateMetadataTableIfNeeded() */
3192 : /************************************************************************/
3193 :
3194 4 : bool OGRPGDataSource::CreateMetadataTableIfNeeded()
3195 : {
3196 4 : if (m_bCreateMetadataTableIfNeededRun)
3197 0 : return m_bCreateMetadataTableIfNeededSuccess;
3198 :
3199 4 : m_bCreateMetadataTableIfNeededRun = true;
3200 :
3201 4 : const bool bIsSuperUser = IsSuperUser();
3202 4 : if (!bIsSuperUser && !OGRSystemTablesEventTriggerExists())
3203 : {
3204 1 : CPLError(CE_Warning, CPLE_AppDefined,
3205 : "User lacks super user privilege to be able to create event "
3206 : "trigger ogr_system_tables_event_trigger_for_metadata");
3207 1 : m_bCreateMetadataTableIfNeededSuccess = true;
3208 1 : return true;
3209 : }
3210 :
3211 : PGresult *hResult;
3212 :
3213 3 : hResult = OGRPG_PQexec(
3214 : hPGConn,
3215 : "SELECT c.oid FROM pg_class c "
3216 : "JOIN pg_namespace n ON c.relnamespace=n.oid "
3217 : "WHERE c.relname = 'metadata' AND n.nspname = 'ogr_system_tables'");
3218 : const bool bFound =
3219 3 : (hResult && PQntuples(hResult) == 1 && !PQgetisnull(hResult, 0, 0));
3220 3 : OGRPGClearResult(hResult);
3221 :
3222 3 : hResult = OGRPG_PQexec(
3223 : hPGConn,
3224 : "SELECT has_database_privilege((select current_database()), 'CREATE')");
3225 : const bool bCanCreateSchema =
3226 6 : (hResult && PQntuples(hResult) == 1 && !PQgetisnull(hResult, 0, 0) &&
3227 3 : strcmp(PQgetvalue(hResult, 0, 0), "t") == 0);
3228 3 : OGRPGClearResult(hResult);
3229 :
3230 3 : if (!bFound)
3231 : {
3232 2 : if (!bCanCreateSchema)
3233 : {
3234 0 : CPLError(CE_Warning, CPLE_AppDefined,
3235 : "User lacks CREATE SCHEMA privilege to be able to create "
3236 : "ogr_system_tables.metadata table");
3237 0 : return false;
3238 : }
3239 : }
3240 : else
3241 : {
3242 1 : if (!HasWritePermissionsOnMetadataTable())
3243 : {
3244 0 : return false;
3245 : }
3246 1 : if (!bCanCreateSchema)
3247 : {
3248 0 : CPLError(CE_Warning, CPLE_AppDefined,
3249 : "User lacks CREATE SCHEMA privilege. Assuming "
3250 : "ogr_system_tables.metadata table has correct structure");
3251 0 : m_bCreateMetadataTableIfNeededSuccess = true;
3252 0 : return true;
3253 : }
3254 : }
3255 :
3256 3 : hResult =
3257 3 : OGRPG_PQexec(hPGConn, "CREATE SCHEMA IF NOT EXISTS ogr_system_tables");
3258 3 : if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK &&
3259 0 : PQresultStatus(hResult) != PGRES_TUPLES_OK))
3260 : {
3261 0 : OGRPGClearResult(hResult);
3262 0 : return false;
3263 : }
3264 3 : OGRPGClearResult(hResult);
3265 :
3266 3 : hResult = OGRPG_PQexec(
3267 : hPGConn, "CREATE TABLE IF NOT EXISTS ogr_system_tables.metadata("
3268 : "id SERIAL, "
3269 : "schema_name TEXT NOT NULL, "
3270 : "table_name TEXT NOT NULL, "
3271 : "metadata TEXT,"
3272 : "UNIQUE(schema_name, table_name))");
3273 3 : if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK &&
3274 0 : PQresultStatus(hResult) != PGRES_TUPLES_OK))
3275 : {
3276 0 : OGRPGClearResult(hResult);
3277 0 : return false;
3278 : }
3279 3 : OGRPGClearResult(hResult);
3280 :
3281 3 : hResult = OGRPG_PQexec(
3282 : hPGConn,
3283 : "DROP FUNCTION IF EXISTS "
3284 : "ogr_system_tables.event_trigger_function_for_metadata() CASCADE");
3285 3 : if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK &&
3286 0 : PQresultStatus(hResult) != PGRES_TUPLES_OK))
3287 : {
3288 0 : OGRPGClearResult(hResult);
3289 0 : return false;
3290 : }
3291 3 : OGRPGClearResult(hResult);
3292 :
3293 3 : hResult = OGRPG_PQexec(
3294 : hPGConn,
3295 : "CREATE FUNCTION "
3296 : "ogr_system_tables.event_trigger_function_for_metadata()\n"
3297 : "RETURNS event_trigger LANGUAGE plpgsql AS $$\n"
3298 : "DECLARE\n"
3299 : " obj record;\n"
3300 : "BEGIN\n"
3301 : " IF has_schema_privilege('ogr_system_tables', 'USAGE') THEN\n"
3302 : " IF has_table_privilege('ogr_system_tables.metadata', 'DELETE') "
3303 : "THEN\n"
3304 : " FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects()\n"
3305 : " LOOP\n"
3306 : " IF obj.object_type = 'table' THEN\n"
3307 : " DELETE FROM ogr_system_tables.metadata m WHERE "
3308 : "m.schema_name = obj.schema_name AND m.table_name = "
3309 : "obj.object_name;\n"
3310 : " END IF;\n"
3311 : " END LOOP;\n"
3312 : " END IF;\n"
3313 : " END IF;\n"
3314 : "END;\n"
3315 : "$$;");
3316 3 : if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK &&
3317 0 : PQresultStatus(hResult) != PGRES_TUPLES_OK))
3318 : {
3319 0 : OGRPGClearResult(hResult);
3320 0 : return false;
3321 : }
3322 3 : OGRPGClearResult(hResult);
3323 :
3324 3 : if (bIsSuperUser)
3325 : {
3326 3 : hResult = OGRPG_PQexec(hPGConn,
3327 : "DROP EVENT TRIGGER IF EXISTS "
3328 : "ogr_system_tables_event_trigger_for_metadata");
3329 3 : if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK &&
3330 0 : PQresultStatus(hResult) != PGRES_TUPLES_OK))
3331 : {
3332 0 : OGRPGClearResult(hResult);
3333 0 : return false;
3334 : }
3335 3 : OGRPGClearResult(hResult);
3336 :
3337 3 : hResult = OGRPG_PQexec(
3338 : hPGConn,
3339 : "CREATE EVENT TRIGGER ogr_system_tables_event_trigger_for_metadata "
3340 : "ON sql_drop "
3341 : "EXECUTE FUNCTION "
3342 : "ogr_system_tables.event_trigger_function_for_metadata()");
3343 3 : if (!hResult || (PQresultStatus(hResult) != PGRES_COMMAND_OK &&
3344 0 : PQresultStatus(hResult) != PGRES_TUPLES_OK))
3345 : {
3346 0 : OGRPGClearResult(hResult);
3347 0 : return false;
3348 : }
3349 3 : OGRPGClearResult(hResult);
3350 : }
3351 :
3352 3 : m_bCreateMetadataTableIfNeededSuccess = true;
3353 3 : m_bOgrSystemTablesMetadataTableExistenceTested = true;
3354 3 : m_bOgrSystemTablesMetadataTableFound = true;
3355 3 : return true;
3356 : }
3357 :
3358 : /************************************************************************/
3359 : /* IsSuperUser() */
3360 : /************************************************************************/
3361 :
3362 4 : bool OGRPGDataSource::IsSuperUser()
3363 : {
3364 4 : PGresult *hResult = OGRPG_PQexec(
3365 4 : hPGConn, "SELECT usesuper FROM pg_user WHERE usename = CURRENT_USER");
3366 : const bool bRet =
3367 7 : (hResult && PQntuples(hResult) == 1 && !PQgetisnull(hResult, 0, 0) &&
3368 3 : strcmp(PQgetvalue(hResult, 0, 0), "t") == 0);
3369 4 : OGRPGClearResult(hResult);
3370 4 : return bRet;
3371 : }
3372 :
3373 : /************************************************************************/
3374 : /* OGRSystemTablesEventTriggerExists() */
3375 : /************************************************************************/
3376 :
3377 1 : bool OGRPGDataSource::OGRSystemTablesEventTriggerExists()
3378 : {
3379 : PGresult *hResult =
3380 1 : OGRPG_PQexec(hPGConn, "SELECT 1 FROM pg_event_trigger WHERE evtname = "
3381 1 : "'ogr_system_tables_event_trigger_for_metadata'");
3382 1 : const bool bRet = (hResult && PQntuples(hResult) == 1);
3383 1 : OGRPGClearResult(hResult);
3384 1 : return bRet;
3385 : }
3386 :
3387 : /************************************************************************/
3388 : /* HasOgrSystemTablesMetadataTable() */
3389 : /************************************************************************/
3390 :
3391 60 : bool OGRPGDataSource::HasOgrSystemTablesMetadataTable()
3392 : {
3393 93 : if (!m_bOgrSystemTablesMetadataTableExistenceTested &&
3394 33 : CPLTestBool(CPLGetConfigOption("OGR_PG_ENABLE_METADATA", "YES")))
3395 : {
3396 30 : m_bOgrSystemTablesMetadataTableExistenceTested = true;
3397 : // Check that the ogr_system_tables.metadata table exists (without
3398 : // causing errors that might abort transactions)
3399 30 : PGresult *hResult = OGRPG_PQexec(
3400 : hPGConn,
3401 : "SELECT c.oid FROM pg_class c "
3402 : "JOIN pg_namespace n ON c.relnamespace=n.oid "
3403 30 : "WHERE c.relname = 'metadata' AND n.nspname = 'ogr_system_tables'");
3404 : const bool bFound =
3405 30 : (hResult && PQntuples(hResult) == 1 && !PQgetisnull(hResult, 0, 0));
3406 30 : OGRPGClearResult(hResult);
3407 30 : if (!bFound)
3408 14 : return false;
3409 :
3410 17 : hResult = OGRPG_PQexec(
3411 : hPGConn,
3412 : "SELECT has_schema_privilege('ogr_system_tables', 'USAGE')");
3413 : const bool bHasSchemaPrivilege =
3414 17 : (hResult && PQntuples(hResult) == 1 &&
3415 51 : !PQgetisnull(hResult, 0, 0) &&
3416 17 : strcmp(PQgetvalue(hResult, 0, 0), "t") == 0);
3417 17 : OGRPGClearResult(hResult);
3418 17 : if (!bHasSchemaPrivilege)
3419 : {
3420 1 : CPLError(CE_Warning, CPLE_AppDefined,
3421 : "Table ogr_system_tables.metadata exists but user lacks "
3422 : "USAGE privilege on ogr_system_tables schema");
3423 1 : return false;
3424 : }
3425 :
3426 16 : hResult = OGRPG_PQexec(
3427 : hPGConn, "SELECT has_table_privilege('ogr_system_tables.metadata', "
3428 : "'SELECT')");
3429 16 : m_bOgrSystemTablesMetadataTableFound =
3430 16 : (hResult && PQntuples(hResult) == 1 &&
3431 48 : !PQgetisnull(hResult, 0, 0) &&
3432 16 : strcmp(PQgetvalue(hResult, 0, 0), "t") == 0);
3433 16 : OGRPGClearResult(hResult);
3434 16 : if (!m_bOgrSystemTablesMetadataTableFound)
3435 : {
3436 0 : CPLError(CE_Warning, CPLE_AppDefined,
3437 : "Table ogr_system_tables.metadata exists but user lacks "
3438 : "SELECT privilege on it");
3439 : }
3440 : }
3441 46 : return m_bOgrSystemTablesMetadataTableFound;
3442 : }
3443 :
3444 : /************************************************************************/
3445 : /* HasWritePermissionsOnMetadataTable() */
3446 : /************************************************************************/
3447 :
3448 12 : bool OGRPGDataSource::HasWritePermissionsOnMetadataTable()
3449 : {
3450 12 : if (!m_bHasWritePermissionsOnMetadataTableRun)
3451 : {
3452 11 : m_bHasWritePermissionsOnMetadataTableRun = true;
3453 :
3454 11 : if (HasOgrSystemTablesMetadataTable())
3455 : {
3456 10 : PGresult *hResult = OGRPG_PQexec(
3457 : hPGConn,
3458 : "SELECT has_table_privilege('ogr_system_tables.metadata', "
3459 : "'INSERT') "
3460 : "AND has_table_privilege('ogr_system_tables.metadata', "
3461 10 : "'DELETE')");
3462 10 : m_bHasWritePermissionsOnMetadataTableSuccess =
3463 10 : (hResult && PQntuples(hResult) == 1 &&
3464 30 : !PQgetisnull(hResult, 0, 0) &&
3465 10 : strcmp(PQgetvalue(hResult, 0, 0), "t") == 0);
3466 10 : OGRPGClearResult(hResult);
3467 10 : if (!m_bHasWritePermissionsOnMetadataTableSuccess)
3468 : {
3469 0 : CPLError(CE_Warning, CPLE_AppDefined,
3470 : "User lacks INSERT and/OR DELETE privilege on "
3471 : "ogr_system_tables.metadata table");
3472 : }
3473 : }
3474 : }
3475 12 : return m_bHasWritePermissionsOnMetadataTableSuccess;
3476 : }
|