LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/pg - ogrpgdatasource.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1290 1467 87.9 %
Date: 2026-02-06 10:43:15 Functions: 56 62 90.3 %

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

Generated by: LCOV version 1.14