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

Generated by: LCOV version 1.14