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

Generated by: LCOV version 1.14