LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/pg - ogrpgdatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1290 1468 87.9 %
Date: 2025-09-10 17:48:50 Functions: 56 62 90.3 %

          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             : }

Generated by: LCOV version 1.14