LCOV - code coverage report
Current view: top level - frmts/postgisraster - postgisrasterdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 210 1527 13.8 %
Date: 2024-05-04 12:52:34 Functions: 11 43 25.6 %

          Line data    Source code
       1             : /***********************************************************************
       2             :  * File :    postgisrasterdataset.cpp
       3             :  * Project:  PostGIS Raster driver
       4             :  * Purpose:  GDAL Dataset implementation for PostGIS Raster driver
       5             :  * Author:   Jorge Arevalo, jorge.arevalo@deimos-space.com
       6             :  *                          jorgearevalo@libregis.org
       7             :  *
       8             :  * Author:       David Zwarg, dzwarg@azavea.com
       9             :  *
      10             :  * Last changes: $Id$
      11             :  *
      12             :  ***********************************************************************
      13             :  * Copyright (c) 2009 - 2013, Jorge Arevalo, David Zwarg
      14             :  * Copyright (c) 2013-2018, Even Rouault <even.rouault at spatialys.com>
      15             :  *
      16             :  * Permission is hereby granted, free of charge, to any person obtaining
      17             :  * a copy of this software and associated documentation files (the
      18             :  * "Software"), to deal in the Software without restriction, including
      19             :  * without limitation the rights to use, copy, modify, merge, publish,
      20             :  * distribute, sublicense, and/or sell copies of the Software, and to
      21             :  * permit persons to whom the Software is furnished to do so, subject to
      22             :  * the following conditions:
      23             :  *
      24             :  * The above copyright notice and this permission notice shall be
      25             :  * included in all copies or substantial portions of the Software.
      26             :  *
      27             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
      28             :  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
      29             :  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
      30             :  * NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
      31             :  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
      32             :  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
      33             :  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
      34             :  * SOFTWARE.
      35             :  **********************************************************************/
      36             : 
      37             : #include "gdal_frmts.h"
      38             : #include "postgisraster.h"
      39             : #include "postgisrasterdrivercore.h"
      40             : #include <math.h>
      41             : 
      42             : #include <algorithm>
      43             : #include <memory>
      44             : 
      45             : #ifdef _WIN32
      46             : #define rint(x) floor((x) + 0.5)
      47             : #endif
      48             : 
      49             : /* PostgreSQL defaults */
      50             : #define DEFAULT_SCHEMA "public"
      51             : #define DEFAULT_COLUMN "rast"
      52             : 
      53             : /** Note on read performance on mode=2:
      54             : 
      55             :     There are different situations:
      56             : 
      57             :      1) the table is registered in the raster_columns table and number of bands,
      58             :    minx,miny,maxx,maxy are available a) no where clause, the table has a primary
      59             :    key and a GIST index on the raster column. If the raster_columns advertise a
      60             :    scale_x and scale_y, use it. Otherwise take the metadata of 10 rasters and
      61             :    compute and average scale_x, scale_y With above information, we can build the
      62             :    dataset definition.
      63             : 
      64             :             (Following logic is implemented in LoadSources())
      65             : 
      66             :             During a IRasterIO() query,
      67             :             i) we will do a SQL query to retrieve the PKID of tiles that
      68             :    intersect the query window. ii) If some tiles are not registered as sources,
      69             :    then do a SQL query to fetch their metadata and instantiate them and register
      70             :    them. iii) If some tiles are not cached, then determine if the query window
      71             :    is not too big (w.r.t. GDAL cache), and if not, then do a SQL query to fetch
      72             :    their raster column.
      73             : 
      74             :             Note: if raster_columns show that all tiles have same dimensions, we
      75             :    can merge the query ii) and iii) in the same one.
      76             : 
      77             :         b) otherwise, do a full scan of metadata to build the sources
      78             : 
      79             :      2) otherwise, throw a warning to the user and do a full scan of metadata is
      80             :    needed to build the sources
      81             : 
      82             :      For 1b) and 2), during a IRasterIO() query, determine which sources are
      83             :    needed and not cached.
      84             : 
      85             :         (Following logic is implemented in IRasterIO())
      86             : 
      87             :         If the query window is not too big,
      88             :             If there's a primary key, then do a SQL query on the IDs of uncached
      89             :    sources to fetch their raster column and cache them. Otherwise if there's a
      90             :    GIST index, do a SQL spatial query on the window to fetch their raster
      91             :    column, and cache them (identification with registered sources is done with
      92             :    the top-left coordinates) Otherwise do a SQL query based on the range of
      93             :    top-left coordinates of tiles that intersect the query window.
      94             : 
      95             :      Conclusion: best performance is achieved with: no where clause, a primary
      96             :    key, a GIST index, known table extent and, with moderate performance
      97             :    advantage :
      98             :                   - same scale_x, scale_y to save an initial SQL query,
      99             :                   - same blocksize_x, blocksize_y to save one SQL query per
     100             :    IRasterIO()
     101             : */
     102             : 
     103             : /************************
     104             :  * \brief Constructor
     105             :  ************************/
     106           2 : PostGISRasterDataset::PostGISRasterDataset()
     107             :     : VRTDataset(0, 0), papszSubdatasets(nullptr), nSrid(-1),
     108             :       nOverviewFactor(1), nBandsToCreate(0), poConn(nullptr),
     109             :       bRegularBlocking(false), bAllTilesSnapToSameGrid(false),
     110             :       bCheckAllTiles(
     111           2 :           CPLTestBool(CPLGetConfigOption("PR_ALLOW_WHOLE_TABLE_SCAN", "YES"))),
     112             :       pszSchema(nullptr), pszTable(nullptr), pszColumn(nullptr),
     113             :       pszWhere(nullptr), pszPrimaryKeyName(nullptr), bIsFastPK(false),
     114             :       bHasTriedFetchingPrimaryKeyName(false),
     115             :       // Default
     116             :       resolutionStrategy(AVERAGE_APPROX_RESOLUTION), nMode(NO_MODE),
     117             :       m_nTiles(0), xmin(0.0), ymin(0.0), xmax(0.0), ymax(0.0),
     118             :       papoSourcesHolders(nullptr), hQuadTree(nullptr),
     119             :       bHasBuiltOverviews(false), nOverviewCount(0), poParentDS(nullptr),
     120             :       papoOverviewDS(nullptr), bAssumeMultiBandReadPattern(true),
     121             :       nNextExpectedBand(1), nXOffPrev(0), nYOffPrev(0), nXSizePrev(0),
     122             :       nYSizePrev(0), bHasTriedHasSpatialIndex(false), bHasSpatialIndex(false),
     123             :       bBuildQuadTreeDynamically(false), bTilesSameDimension(false),
     124           4 :       nTileWidth(0), nTileHeight(0)
     125             : {
     126           2 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     127             : 
     128           2 :     adfGeoTransform[GEOTRSFRM_TOPLEFT_X] = 0.0;
     129           2 :     adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1] = 0.0;
     130           2 :     adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] = 0.0;
     131           2 :     adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] = 0.0;
     132             :     // coverity[tainted_data]
     133           2 :     adfGeoTransform[GEOTRSFRM_WE_RES] =
     134           2 :         CPLAtof(CPLGetConfigOption("PR_WE_RES", NO_VALID_RES));
     135             :     // coverity[tainted_data]
     136           2 :     adfGeoTransform[GEOTRSFRM_NS_RES] =
     137           2 :         CPLAtof(CPLGetConfigOption("PR_NS_RES", NO_VALID_RES));
     138             : 
     139           2 :     const char *pszTmp = nullptr;
     140             :     // We ignore this option if we provided the desired resolution
     141           2 :     if (CPLIsEqual(adfGeoTransform[GEOTRSFRM_WE_RES], CPLAtof(NO_VALID_RES)) ||
     142           0 :         CPLIsEqual(adfGeoTransform[GEOTRSFRM_NS_RES], CPLAtof(NO_VALID_RES)))
     143             :     {
     144             : 
     145             :         // Resolution didn't have a valid value, so, we initiate it
     146           2 :         adfGeoTransform[GEOTRSFRM_WE_RES] = 0.0;
     147           2 :         adfGeoTransform[GEOTRSFRM_NS_RES] = 0.0;
     148             : 
     149           2 :         pszTmp = CPLGetConfigOption("PR_RESOLUTION_STRATEGY", "AVERAGE_APPROX");
     150             : 
     151           2 :         if (EQUAL(pszTmp, "LOWEST"))
     152           0 :             resolutionStrategy = LOWEST_RESOLUTION;
     153             : 
     154           2 :         else if (EQUAL(pszTmp, "HIGHEST"))
     155           0 :             resolutionStrategy = HIGHEST_RESOLUTION;
     156             : 
     157           2 :         else if (EQUAL(pszTmp, "USER"))
     158           0 :             resolutionStrategy = USER_RESOLUTION;
     159             : 
     160           2 :         else if (EQUAL(pszTmp, "AVERAGE"))
     161           0 :             resolutionStrategy = AVERAGE_RESOLUTION;
     162             :     }
     163             :     else
     164             :     {
     165           0 :         resolutionStrategy = USER_RESOLUTION;
     166             : #ifdef DEBUG_VERBOSE
     167             :         pszTmp = "USER";
     168             : #endif
     169             :     }
     170             : 
     171             : #ifdef DEBUG_VERBOSE
     172             :     CPLDebug("PostGIS_Raster",
     173             :              "PostGISRasterDataset::Constructor:"
     174             :              "STRATEGY = %s",
     175             :              pszTmp);
     176             : #endif
     177             : 
     178           2 :     poDriver = nullptr;
     179             : 
     180           2 :     nRasterXSize = 0;
     181           2 :     nRasterYSize = 0;
     182             : 
     183           2 :     SetWritable(false);
     184             : 
     185             :     // TODO: Parametrize bAllTilesSnapToSameGrid. It controls if all the
     186             :     // raster rows, in ONE_RASTER_PER_TABLE mode, must be checked to
     187             :     // test if they snap to the same grid and have the same SRID. It can
     188             :     // be the user decision, if he/she's sure all the rows pass the
     189             :     // test and want more speed.
     190           2 : }
     191             : 
     192             : /************************
     193             :  * \brief Constructor
     194             :  ************************/
     195           4 : PostGISRasterDataset::~PostGISRasterDataset()
     196             : {
     197             : 
     198           2 :     if (pszSchema)
     199             :     {
     200           2 :         CPLFree(pszSchema);
     201           2 :         pszSchema = nullptr;
     202             :     }
     203             : 
     204           2 :     if (pszTable)
     205             :     {
     206           2 :         CPLFree(pszTable);
     207           2 :         pszTable = nullptr;
     208             :     }
     209             : 
     210           2 :     if (pszColumn)
     211             :     {
     212           2 :         CPLFree(pszColumn);
     213           2 :         pszColumn = nullptr;
     214             :     }
     215             : 
     216           2 :     if (pszWhere)
     217             :     {
     218           0 :         CPLFree(pszWhere);
     219           0 :         pszWhere = nullptr;
     220             :     }
     221             : 
     222           2 :     if (pszPrimaryKeyName)
     223             :     {
     224           0 :         CPLFree(pszPrimaryKeyName);
     225           0 :         pszPrimaryKeyName = nullptr;
     226             :     }
     227             : 
     228           2 :     if (papszSubdatasets)
     229             :     {
     230           0 :         CSLDestroy(papszSubdatasets);
     231           0 :         papszSubdatasets = nullptr;
     232             :     }
     233             : 
     234           2 :     if (hQuadTree)
     235             :     {
     236           0 :         CPLQuadTreeDestroy(hQuadTree);
     237           0 :         hQuadTree = nullptr;
     238             :     }
     239             : 
     240             :     // Call it now so that the VRT sources
     241             :     // are deleted and that there is no longer any code
     242             :     // referencing the bands of the source holders.
     243             :     // Otherwise this would go wrong because
     244             :     // of the deleting the source holders just below.
     245           2 :     PostGISRasterDataset::CloseDependentDatasets();
     246             : 
     247           2 :     if (papoSourcesHolders)
     248             :     {
     249             :         int i;
     250           0 :         for (i = 0; i < m_nTiles; i++)
     251             :         {
     252           0 :             if (papoSourcesHolders[i])
     253           0 :                 delete papoSourcesHolders[i];
     254             :         }
     255             : 
     256           0 :         VSIFree(papoSourcesHolders);
     257           0 :         papoSourcesHolders = nullptr;
     258             :     }
     259           4 : }
     260             : 
     261             : /************************************************************************/
     262             : /*                        CloseDependentDatasets()                      */
     263             : /************************************************************************/
     264             : 
     265           2 : int PostGISRasterDataset::CloseDependentDatasets()
     266             : {
     267           2 :     int bHasDroppedRef = VRTDataset::CloseDependentDatasets();
     268           2 :     if (nOverviewCount > 0)
     269             :     {
     270             :         int i;
     271           0 :         for (i = 0; i < nOverviewCount; i++)
     272             :         {
     273           0 :             delete papoOverviewDS[i];
     274             :         }
     275           0 :         CPLFree(papoOverviewDS);
     276           0 :         papoOverviewDS = nullptr;
     277           0 :         nOverviewCount = 0;
     278           0 :         bHasDroppedRef = TRUE;
     279             :     }
     280           2 :     if (!oOutDBDatasetCache.empty())
     281             :     {
     282           0 :         oOutDBDatasetCache.clear();
     283           0 :         bHasDroppedRef = TRUE;
     284             :     }
     285             : 
     286           2 :     return bHasDroppedRef;
     287             : }
     288             : 
     289             : /************************************************************************/
     290             : /*                            FlushCache()                              */
     291             : /************************************************************************/
     292             : 
     293           2 : CPLErr PostGISRasterDataset::FlushCache(bool bAtClosing)
     294             : {
     295           2 :     const CPLErr eErr = VRTDataset::FlushCache(bAtClosing);
     296           2 :     oOutDBDatasetCache.clear();
     297           2 :     return eErr;
     298             : }
     299             : 
     300             : /************************************************************************/
     301             : /*                            HasSpatialIndex()                         */
     302             : /************************************************************************/
     303             : 
     304           0 : GBool PostGISRasterDataset::HasSpatialIndex()
     305             : {
     306           0 :     CPLString osCommand;
     307           0 :     PGresult *poResult = nullptr;
     308             : 
     309             :     // If exists, return it
     310           0 :     if (bHasTriedHasSpatialIndex)
     311             :     {
     312           0 :         return bHasSpatialIndex;
     313             :     }
     314             : 
     315           0 :     bHasTriedHasSpatialIndex = true;
     316             : 
     317             :     /* For debugging purposes only */
     318           0 :     if (CPLTestBool(CPLGetConfigOption("PR_DISABLE_GIST", "FALSE")))
     319           0 :         return false;
     320             : 
     321             :     // Copyright dustymugs !!!
     322             :     osCommand.Printf(
     323             :         "SELECT n.nspname AS schema_name, c2.relname AS table_name, "
     324             :         "att.attname AS column_name, "
     325             :         "       c.relname AS index_name, am.amname AS index_type "
     326             :         "FROM pg_catalog.pg_class c "
     327             :         "JOIN pg_catalog.pg_index i ON i.indexrelid = c.oid "
     328             :         "JOIN pg_catalog.pg_class c2 ON i.indrelid = c2.oid "
     329             :         "JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace "
     330             :         "JOIN pg_am am ON c.relam = am.oid "
     331             :         "JOIN pg_attribute att ON att.attrelid = c2.oid "
     332             :         "AND pg_catalog.format_type(att.atttypid, att.atttypmod) = 'raster' "
     333             :         "WHERE c.relkind IN ('i') "
     334             :         "AND am.amname = 'gist' "
     335             :         "AND strpos(split_part(pg_catalog.pg_get_indexdef(i.indexrelid, 0, "
     336             :         "true), ' gist ', 2), att.attname) > 0 "
     337             :         "AND n.nspname = '%s' "
     338             :         "AND c2.relname = '%s' "
     339             :         "AND att.attname = '%s' ",
     340           0 :         pszSchema, pszTable, pszColumn);
     341             : 
     342             : #ifdef DEBUG_QUERY
     343             :     CPLDebug("PostGIS_Raster",
     344             :              "PostGISRasterDataset::HasSpatialIndex(): Query: %s",
     345             :              osCommand.c_str());
     346             : #endif
     347             : 
     348           0 :     poResult = PQexec(poConn, osCommand.c_str());
     349             : 
     350           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     351           0 :         PQntuples(poResult) <= 0)
     352             :     {
     353           0 :         bHasSpatialIndex = false;
     354           0 :         CPLDebug("PostGIS_Raster",
     355             :                  "For better performance, creating a spatial index "
     356             :                  "with 'CREATE INDEX %s_%s_%s_gist_idx ON %s.%s USING GIST "
     357             :                  "(ST_ConvexHull(%s))' is advised",
     358             :                  pszSchema, pszTable, pszColumn, pszSchema, pszTable,
     359             :                  pszColumn);
     360             :     }
     361             :     else
     362             :     {
     363           0 :         bHasSpatialIndex = true;
     364             :     }
     365             : 
     366           0 :     if (poResult)
     367           0 :         PQclear(poResult);
     368           0 :     return bHasSpatialIndex;
     369             : }
     370             : 
     371             : /***********************************************************************
     372             :  * \brief Look for a primary key in the table selected by the user
     373             :  *
     374             :  * If the table does not have a primary key, it returns NULL
     375             :  **********************************************************************/
     376           0 : const char *PostGISRasterDataset::GetPrimaryKeyRef()
     377             : {
     378           0 :     CPLString osCommand;
     379           0 :     PGresult *poResult = nullptr;
     380             : 
     381             :     // If exists, return it
     382           0 :     if (bHasTriedFetchingPrimaryKeyName)
     383             :     {
     384           0 :         return pszPrimaryKeyName;
     385             :     }
     386             : 
     387           0 :     bHasTriedFetchingPrimaryKeyName = true;
     388             : 
     389             :     /* For debugging purposes only */
     390           0 :     if (CPLTestBool(CPLGetConfigOption("PR_DISABLE_PK", "FALSE")))
     391           0 :         return nullptr;
     392             : 
     393             :     /* Determine the primary key/unique column on the table */
     394             :     osCommand.Printf(
     395             :         "select d.attname from pg_catalog.pg_constraint "
     396             :         "as a join pg_catalog.pg_indexes as b on a.conname = "
     397             :         "b.indexname join pg_catalog.pg_class as c on c.relname = "
     398             :         "b.tablename join pg_catalog.pg_attribute as d on "
     399             :         "c.relfilenode = d.attrelid where b.schemaname = '%s' and "
     400             :         "b.tablename = '%s' and d.attnum = a.conkey[1] and a.contype "
     401             :         "in ('p', 'u')",
     402           0 :         pszSchema, pszTable);
     403             : 
     404             : #ifdef DEBUG_QUERY
     405             :     CPLDebug("PostGIS_Raster",
     406             :              "PostGISRasterDataset::GetPrimaryKeyRef(): Query: %s",
     407             :              osCommand.c_str());
     408             : #endif
     409             : 
     410           0 :     poResult = PQexec(poConn, osCommand.c_str());
     411             : 
     412           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     413           0 :         PQntuples(poResult) <= 0)
     414             :     {
     415             : 
     416           0 :         PQclear(poResult);
     417             : 
     418             :         /**
     419             :          * Maybe there is no primary key or unique constraint; a
     420             :          * sequence will also suffice; get the first one
     421             :          **/
     422             : 
     423             :         osCommand.Printf(
     424             :             "select cols.column_name from "
     425             :             "information_schema.columns as cols join "
     426             :             "information_schema.sequences as seqs on "
     427             :             "cols.column_default like '%%'||seqs.sequence_name||'%%' "
     428             :             "where cols.table_schema = '%s' and cols.table_name = '%s'",
     429           0 :             pszSchema, pszTable);
     430             : 
     431             : #ifdef DEBUG_QUERY
     432             :         CPLDebug("PostGIS_Raster",
     433             :                  "PostGISRasterDataset::GetPrimaryKeyRef(): Query: %s",
     434             :                  osCommand.c_str());
     435             : #endif
     436             : 
     437           0 :         poResult = PQexec(poConn, osCommand.c_str());
     438             : 
     439           0 :         if (poResult == nullptr ||
     440           0 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     441           0 :             PQntuples(poResult) <= 0)
     442             :         {
     443             : 
     444           0 :             CPLDebug("PostGIS_Raster",
     445             :                      "PostGISRasterDataset::GetPrimaryKeyRef(): Could not "
     446             :                      "find a primary key or unique column on the specified "
     447             :                      "table %s.%s. For better performance, creating a primary "
     448             :                      "key on the table is advised.",
     449             :                      pszSchema, pszTable);
     450             : 
     451           0 :             pszPrimaryKeyName = nullptr;  // Just in case
     452             :         }
     453             :         else
     454             :         {
     455           0 :             pszPrimaryKeyName = CPLStrdup(PQgetvalue(poResult, 0, 0));
     456             :         }
     457             :     }
     458             : 
     459             :     // Ok, get the primary key
     460             :     else
     461             :     {
     462           0 :         pszPrimaryKeyName = CPLStrdup(PQgetvalue(poResult, 0, 0));
     463           0 :         bIsFastPK = true;
     464             :     }
     465             : 
     466           0 :     PQclear(poResult);
     467             : 
     468           0 :     return pszPrimaryKeyName;
     469             : }
     470             : 
     471             : /***********************************************************************
     472             :  * \brief Look for raster tables in database and store them as
     473             :  * subdatasets
     474             :  *
     475             :  * If no table is provided in connection string, the driver looks for
     476             :  * the existent raster tables in the schema given as argument. This
     477             :  * argument, however, is optional. If a NULL value is provided, the
     478             :  * driver looks for all raster tables in all schemas of the
     479             :  * user-provided database.
     480             :  *
     481             :  * NOTE: Permissions are managed by libpq. The driver only returns an
     482             :  * error if an error is returned when trying to access to tables not
     483             :  * allowed to the current user.
     484             :  **********************************************************************/
     485           0 : GBool PostGISRasterDataset::BrowseDatabase(const char *pszCurrentSchema,
     486             :                                            const char *pszValidConnectionString)
     487             : {
     488             : 
     489           0 :     char *l_pszSchema = nullptr;
     490           0 :     char *l_pszTable = nullptr;
     491           0 :     char *l_pszColumn = nullptr;
     492             : 
     493           0 :     int i = 0;
     494           0 :     int nTuples = 0;
     495           0 :     PGresult *poResult = nullptr;
     496           0 :     CPLString osCommand;
     497             : 
     498             :     /*************************************************************
     499             :      * Fetch all the raster tables and store them as subdatasets
     500             :      *************************************************************/
     501           0 :     if (pszCurrentSchema == nullptr)
     502             :     {
     503             :         osCommand.Printf(
     504             :             "select pg_namespace.nspname as schema, "
     505             :             "pg_class.relname as table, pg_attribute.attname as column "
     506             :             "from pg_class, pg_namespace,pg_attribute, pg_type where "
     507             :             "pg_class.relnamespace = pg_namespace.oid and "
     508             :             "pg_class.oid = pg_attribute.attrelid and "
     509             :             "pg_attribute.atttypid = pg_type.oid and "
     510           0 :             "pg_type.typname = 'raster'");
     511             : 
     512           0 :         poResult = PQexec(poConn, osCommand.c_str());
     513           0 :         if (poResult == nullptr ||
     514           0 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     515           0 :             PQntuples(poResult) <= 0)
     516             :         {
     517             : 
     518           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     519             :                         "Error browsing database for PostGIS Raster tables: %s",
     520           0 :                         PQerrorMessage(poConn));
     521           0 :             if (poResult != nullptr)
     522           0 :                 PQclear(poResult);
     523             : 
     524           0 :             return false;
     525             :         }
     526             : 
     527           0 :         nTuples = PQntuples(poResult);
     528           0 :         for (i = 0; i < nTuples; i++)
     529             :         {
     530           0 :             l_pszSchema = PQgetvalue(poResult, i, 0);
     531           0 :             l_pszTable = PQgetvalue(poResult, i, 1);
     532           0 :             l_pszColumn = PQgetvalue(poResult, i, 2);
     533             : 
     534           0 :             papszSubdatasets = CSLSetNameValue(
     535             :                 papszSubdatasets, CPLSPrintf("SUBDATASET_%d_NAME", (i + 1)),
     536             :                 CPLSPrintf("PG:%s schema='%s' table='%s' column='%s'",
     537             :                            pszValidConnectionString, l_pszSchema, l_pszTable,
     538             :                            l_pszColumn));
     539             : 
     540           0 :             papszSubdatasets = CSLSetNameValue(
     541             :                 papszSubdatasets, CPLSPrintf("SUBDATASET_%d_DESC", (i + 1)),
     542             :                 CPLSPrintf("PostGIS Raster table at %s.%s (%s)", l_pszSchema,
     543             :                            l_pszTable, l_pszColumn));
     544             :         }
     545             : 
     546           0 :         PQclear(poResult);
     547             :     }
     548             :     /***************************************************************
     549             :      * Fetch all the schema's raster tables and store them as
     550             :      * subdatasets
     551             :      **************************************************************/
     552             :     else
     553             :     {
     554             :         osCommand.Printf("select pg_class.relname as table, "
     555             :                          "pg_attribute.attname as column from pg_class, "
     556             :                          "pg_namespace,pg_attribute, pg_type where "
     557             :                          "pg_class.relnamespace = pg_namespace.oid and "
     558             :                          "pg_class.oid = pg_attribute.attrelid and "
     559             :                          "pg_attribute.atttypid = pg_type.oid and "
     560             :                          "pg_type.typname = 'raster' and "
     561             :                          "pg_namespace.nspname = '%s'",
     562           0 :                          pszCurrentSchema);
     563             : 
     564           0 :         poResult = PQexec(poConn, osCommand.c_str());
     565           0 :         if (poResult == nullptr ||
     566           0 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     567           0 :             PQntuples(poResult) <= 0)
     568             :         {
     569             : 
     570           0 :             ReportError(CE_Failure, CPLE_AppDefined,
     571             :                         "Error browsing database for PostGIS Raster tables: %s",
     572           0 :                         PQerrorMessage(poConn));
     573             : 
     574           0 :             if (poResult != nullptr)
     575           0 :                 PQclear(poResult);
     576             : 
     577           0 :             return false;
     578             :         }
     579             : 
     580           0 :         nTuples = PQntuples(poResult);
     581           0 :         for (i = 0; i < nTuples; i++)
     582             :         {
     583           0 :             l_pszTable = PQgetvalue(poResult, i, 0);
     584           0 :             l_pszColumn = PQgetvalue(poResult, i, 1);
     585             : 
     586           0 :             papszSubdatasets = CSLSetNameValue(
     587             :                 papszSubdatasets, CPLSPrintf("SUBDATASET_%d_NAME", (i + 1)),
     588             :                 CPLSPrintf("PG:%s schema='%s' table='%s' column='%s'",
     589             :                            pszValidConnectionString, pszCurrentSchema,
     590             :                            l_pszTable, l_pszColumn));
     591             : 
     592           0 :             papszSubdatasets = CSLSetNameValue(
     593             :                 papszSubdatasets, CPLSPrintf("SUBDATASET_%d_DESC", (i + 1)),
     594             :                 CPLSPrintf("PostGIS Raster table at %s.%s (%s)",
     595             :                            pszCurrentSchema, l_pszTable, l_pszColumn));
     596             :         }
     597             : 
     598           0 :         PQclear(poResult);
     599             :     }
     600             : 
     601           0 :     return true;
     602             : }
     603             : 
     604             : /***********************************************************************
     605             :  * \brief Look for overview tables for the bands of the current dataset
     606             :  **********************************************************************/
     607           0 : PROverview *PostGISRasterDataset::GetOverviewTables(int *pnOverviews)
     608             : {
     609           0 :     PROverview *poOV = nullptr;
     610           0 :     CPLString osCommand;
     611             : 
     612             :     osCommand.Printf("SELECT o_table_name, overview_factor, "
     613             :                      "o_raster_column, o_table_schema FROM raster_overviews "
     614             :                      "WHERE r_table_schema = '%s' AND r_table_name = '%s' AND "
     615             :                      "r_raster_column = '%s' ORDER BY overview_factor",
     616           0 :                      this->pszSchema, this->pszTable, this->pszColumn);
     617             : 
     618             : #ifdef DEBUG_QUERY
     619             :     CPLDebug("PostGIS_Raster",
     620             :              "PostGISRasterDataset::GetOverviewTables(): Query: %s",
     621             :              osCommand.c_str());
     622             : #endif
     623             : 
     624           0 :     PGresult *poResult = PQexec(poConn, osCommand.c_str());
     625             : 
     626           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
     627           0 :         PQntuples(poResult) < 0)
     628             :     {
     629             : 
     630           0 :         ReportError(CE_Failure, CPLE_AppDefined,
     631             :                     "Error looking for overview tables: %s",
     632           0 :                     PQerrorMessage(poConn));
     633             : 
     634           0 :         if (poResult)
     635           0 :             PQclear(poResult);
     636             : 
     637           0 :         return nullptr;
     638             :     }
     639             : 
     640           0 :     else if (PQntuples(poResult) == 0)
     641             :     {
     642           0 :         CPLDebug("PostGIS_Raster",
     643             :                  "PostGISRasterDataset::GetOverviewTables(): No overviews "
     644             :                  "for table %s.%s",
     645             :                  pszTable, pszSchema);
     646             : 
     647           0 :         if (poResult)
     648           0 :             PQclear(poResult);
     649             : 
     650           0 :         return nullptr;
     651             :     }
     652             : 
     653           0 :     int nTuples = PQntuples(poResult);
     654             : 
     655           0 :     poOV = static_cast<PROverview *>(VSIMalloc2(nTuples, sizeof(PROverview)));
     656           0 :     if (poOV == nullptr)
     657             :     {
     658           0 :         ReportError(CE_Failure, CPLE_AppDefined,
     659             :                     "Error looking for overview tables");
     660             : 
     661           0 :         PQclear(poResult);
     662             : 
     663           0 :         return nullptr;
     664             :     }
     665             : 
     666           0 :     int iOVerview = 0;
     667           0 :     for (iOVerview = 0; iOVerview < nTuples; iOVerview++)
     668             :     {
     669           0 :         poOV[iOVerview].pszSchema =
     670           0 :             CPLStrdup(PQgetvalue(poResult, iOVerview, 3));
     671             : 
     672           0 :         poOV[iOVerview].pszTable =
     673           0 :             CPLStrdup(PQgetvalue(poResult, iOVerview, 0));
     674             : 
     675           0 :         poOV[iOVerview].pszColumn =
     676           0 :             CPLStrdup(PQgetvalue(poResult, iOVerview, 2));
     677             : 
     678           0 :         poOV[iOVerview].nFactor = atoi(PQgetvalue(poResult, iOVerview, 1));
     679             :     }
     680             : 
     681           0 :     if (pnOverviews)
     682           0 :         *pnOverviews = nTuples;
     683             : 
     684           0 :     PQclear(poResult);
     685             : 
     686           0 :     return poOV;
     687             : }
     688             : 
     689             : /***********************************************************************
     690             :  * \brief Build overview datasets
     691             :  ***********************************************************************/
     692           0 : void PostGISRasterDataset::BuildOverviews()
     693             : {
     694           0 :     if (bHasBuiltOverviews || poParentDS != nullptr)
     695           0 :         return;
     696             : 
     697           0 :     bHasBuiltOverviews = true;
     698             :     /*******************************************************************
     699             :      * We also get the names of the overview tables, if they exist. So,
     700             :      * we'll can use them to create the overview datasets.
     701             :      ******************************************************************/
     702           0 :     int nOV = 0;
     703           0 :     PROverview *poOV = GetOverviewTables(&nOV);
     704             : 
     705           0 :     if (poOV)
     706             :     {
     707           0 :         papoOverviewDS = static_cast<PostGISRasterDataset **>(
     708           0 :             CPLCalloc(nOV, sizeof(PostGISRasterDataset *)));
     709           0 :         nOverviewCount = 0;
     710             : 
     711             :         int iOV;
     712           0 :         for (iOV = 0; iOV < nOV; iOV++)
     713             :         {
     714           0 :             PostGISRasterDataset *poOvrDS = new PostGISRasterDataset();
     715           0 :             poOvrDS->ShareLockWithParentDataset(this);
     716           0 :             poOvrDS->nOverviewFactor = poOV[iOV].nFactor;
     717           0 :             poOvrDS->poConn = poConn;
     718           0 :             poOvrDS->eAccess = eAccess;
     719           0 :             poOvrDS->eOutDBResolution = eOutDBResolution;
     720           0 :             poOvrDS->bHasStBandFileSize = bHasStBandFileSize;
     721           0 :             poOvrDS->nMode = nMode;
     722           0 :             poOvrDS->pszSchema = poOV[iOV].pszSchema;  // takes ownership
     723           0 :             poOvrDS->pszTable = poOV[iOV].pszTable;    // takes ownership
     724           0 :             poOvrDS->pszColumn = poOV[iOV].pszColumn;  // takes ownership
     725           0 :             poOvrDS->pszWhere = pszWhere ? CPLStrdup(pszWhere) : nullptr;
     726           0 :             poOvrDS->poParentDS = this;
     727             : 
     728           0 :             if (!CPLTestBool(
     729           0 :                     CPLGetConfigOption("PG_DEFERRED_OVERVIEWS", "YES")) &&
     730           0 :                 (!poOvrDS->SetRasterProperties(nullptr) ||
     731           0 :                  poOvrDS->GetRasterCount() != GetRasterCount()))
     732             :             {
     733           0 :                 delete poOvrDS;
     734             :             }
     735             :             else
     736             :             {
     737           0 :                 papoOverviewDS[nOverviewCount++] = poOvrDS;
     738             :             }
     739             :         }
     740             : 
     741           0 :         VSIFree(poOV);
     742             :     }
     743             : }
     744             : 
     745             : /***********************************************************************
     746             :  * \brief Returns overview count
     747             :  ***********************************************************************/
     748           0 : int PostGISRasterDataset::GetOverviewCount()
     749             : {
     750           0 :     BuildOverviews();
     751           0 :     return nOverviewCount;
     752             : }
     753             : 
     754             : /***********************************************************************
     755             :  * \brief Returns overview dataset
     756             :  ***********************************************************************/
     757           0 : PostGISRasterDataset *PostGISRasterDataset::GetOverviewDS(int iOvr)
     758             : {
     759           0 :     if (iOvr < 0 || iOvr > GetOverviewCount())
     760           0 :         return nullptr;
     761           0 :     return papoOverviewDS[iOvr];
     762             : }
     763             : 
     764             : /***********************************************************************
     765             :  * \brief Calculates the destination window for a VRT source, taking
     766             :  * into account that the source is a PostGIS Raster tile and the
     767             :  * destination is the general dataset itself
     768             :  *
     769             :  * This method is adapted from gdalbuildvrt as is in GDAL 1.10.0
     770             :  ***********************************************************************/
     771           0 : void PostGISRasterDataset::GetDstWin(PostGISRasterTileDataset *psDP,
     772             :                                      int *pnDstXOff, int *pnDstYOff,
     773             :                                      int *pnDstXSize, int *pnDstYSize)
     774             : {
     775           0 :     double we_res = this->adfGeoTransform[GEOTRSFRM_WE_RES];
     776           0 :     double ns_res = this->adfGeoTransform[GEOTRSFRM_NS_RES];
     777             : 
     778             :     double adfTileGeoTransform[6];
     779           0 :     psDP->GetGeoTransform(adfTileGeoTransform);
     780             : 
     781           0 :     *pnDstXOff = static_cast<int>(
     782           0 :         0.5 + (adfTileGeoTransform[GEOTRSFRM_TOPLEFT_X] - xmin) / we_res);
     783             : 
     784           0 :     if (ns_res < 0)
     785           0 :         *pnDstYOff = static_cast<int>(
     786           0 :             0.5 + (ymax - adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y]) / -ns_res);
     787             :     else
     788           0 :         *pnDstYOff = static_cast<int>(
     789           0 :             0.5 + (adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y] - ymin) / ns_res);
     790             : 
     791           0 :     *pnDstXSize = static_cast<int>(
     792           0 :         0.5 + psDP->GetRasterXSize() * adfTileGeoTransform[GEOTRSFRM_WE_RES] /
     793             :                   we_res);
     794           0 :     *pnDstYSize = static_cast<int>(
     795           0 :         0.5 + psDP->GetRasterYSize() * adfTileGeoTransform[GEOTRSFRM_NS_RES] /
     796             :                   ns_res);
     797           0 : }
     798             : 
     799             : /***********************************************************************
     800             :  * \brief Add tiles bands as complex source for raster bands.
     801             :  **********************************************************************/
     802           0 : void PostGISRasterDataset::AddComplexSource(PostGISRasterTileDataset *poRTDS)
     803             : {
     804             :     // Parameters to add the tile bands as sources
     805           0 :     int nDstXOff = 0;
     806           0 :     int nDstYOff = 0;
     807           0 :     int nDstXSize = 0;
     808           0 :     int nDstYSize = 0;
     809             : 
     810             :     // Get src and dst parameters
     811           0 :     GetDstWin(poRTDS, &nDstXOff, &nDstYOff, &nDstXSize, &nDstYSize);
     812             : 
     813             : #ifdef DEBUG_VERBOSE
     814             :     CPLDebug("PostGIS_Raster",
     815             :              "PostGISRasterDataset::AddComplexSource: "
     816             :              "Tile bounding box from (%d, %d) of size (%d, %d) will "
     817             :              "cover raster bounding box from (%d, %d) of size "
     818             :              "(%d, %d)",
     819             :              0, 0, poRTDS->GetRasterXSize(), poRTDS->GetRasterYSize(), nDstXOff,
     820             :              nDstYOff, nDstXSize, nDstYSize);
     821             : #endif
     822             : 
     823             :     // Add tiles bands as sources for the raster bands
     824           0 :     for (int iBand = 0; iBand < nBandsToCreate; iBand++)
     825             :     {
     826             :         PostGISRasterRasterBand *prb =
     827           0 :             cpl::down_cast<PostGISRasterRasterBand *>(GetRasterBand(iBand + 1));
     828             : 
     829           0 :         int bHasNoData = FALSE;
     830           0 :         double dfBandNoDataValue = prb->GetNoDataValue(&bHasNoData);
     831             : 
     832             :         PostGISRasterTileRasterBand *prtb =
     833           0 :             cpl::down_cast<PostGISRasterTileRasterBand *>(
     834             :                 poRTDS->GetRasterBand(iBand + 1));
     835             : 
     836           0 :         prb->AddComplexSource(
     837           0 :             prtb, 0, 0, poRTDS->GetRasterXSize(), poRTDS->GetRasterYSize(),
     838             :             nDstXOff, nDstYOff, nDstXSize, nDstYSize, 0.0, 1.0,
     839           0 :             (bHasNoData) ? dfBandNoDataValue : VRT_NODATA_UNSET);
     840             : 
     841           0 :         prtb->poSource = prb->papoSources[prb->nSources - 1];
     842             :     }
     843           0 : }
     844             : 
     845             : /************************************************************************/
     846             : /*                         GetMatchingSourceRef()                       */
     847             : /************************************************************************/
     848             : 
     849             : /**
     850             :  * \brief Get the tile dataset that matches the given upper left pixel
     851             :  **/
     852             : PostGISRasterTileDataset *
     853           0 : PostGISRasterDataset::GetMatchingSourceRef(double dfUpperLeftX,
     854             :                                            double dfUpperLeftY)
     855             : {
     856             :     int i;
     857           0 :     PostGISRasterTileDataset *poRTDS = nullptr;
     858             : 
     859           0 :     for (i = 0; i < m_nTiles; i++)
     860             :     {
     861           0 :         poRTDS = papoSourcesHolders[i];
     862             : 
     863           0 :         if (CPLIsEqual(poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_X],
     864           0 :                        dfUpperLeftX) &&
     865           0 :             CPLIsEqual(poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_Y],
     866             :                        dfUpperLeftY))
     867             :         {
     868             : 
     869           0 :             return poRTDS;
     870             :         }
     871             :     }
     872             : 
     873           0 :     return nullptr;
     874             : }
     875             : 
     876             : /************************************************************************/
     877             : /*                           CacheTile()                                */
     878             : /************************************************************************/
     879           0 : void PostGISRasterDataset::CacheTile(const char *pszMetadata,
     880             :                                      const char *pszRaster, const char *pszPKID,
     881             :                                      int nBand, bool bAllBandCaching)
     882             : {
     883             :     /**
     884             :      * Get metadata record and unpack it
     885             :      **/
     886           0 :     char *pszRes = CPLStrdup(pszMetadata);
     887             : 
     888             :     // Skip first "("
     889           0 :     char *pszFilteredRes = pszRes + 1;
     890             : 
     891             :     // Skip last ")"
     892           0 :     pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
     893             : 
     894             :     // Tokenize
     895           0 :     char **papszParams = CSLTokenizeString2(
     896             :         pszFilteredRes, ",", CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS);
     897           0 :     CPLAssert(CSLCount(papszParams) >= ELEMENTS_OF_METADATA_RECORD);
     898             : 
     899           0 :     CPLFree(pszRes);
     900             : 
     901           0 :     const double dfTileUpperLeftX = CPLAtof(papszParams[POS_UPPERLEFTX]);
     902           0 :     const double dfTileUpperLeftY = CPLAtof(papszParams[POS_UPPERLEFTY]);
     903           0 :     const double dfTileResX = CPLAtof(papszParams[POS_SCALEX]);
     904           0 :     const double dfTileResY = CPLAtof(papszParams[POS_SCALEY]);
     905           0 :     const int nTileXSize = atoi(papszParams[POS_WIDTH]);
     906           0 :     const int nTileYSize = atoi(papszParams[POS_HEIGHT]);
     907             : 
     908           0 :     CSLDestroy(papszParams);
     909           0 :     papszParams = nullptr;
     910             : 
     911             :     /**
     912             :      * Get actual raster band data
     913             :      **/
     914           0 :     const GDALDataType eDT = GetRasterBand(nBand)->GetRasterDataType();
     915           0 :     const int nBandDataTypeSize = GDALGetDataTypeSizeBytes(eDT);
     916           0 :     const int nExpectedBandDataSize =
     917           0 :         nTileXSize * nTileYSize * nBandDataTypeSize;
     918           0 :     const int nExpectedBands = bAllBandCaching ? GetRasterCount() : 1;
     919             : 
     920           0 :     int nWKBLength = 0;
     921             : 
     922             :     struct CPLFreer
     923             :     {
     924           0 :         void operator()(GByte *x) const
     925             :         {
     926           0 :             CPLFree(x);
     927           0 :         }
     928             :     };
     929             : 
     930             :     std::unique_ptr<GByte, CPLFreer> pbyDataAutoFreed(
     931           0 :         CPLHexToBinary(pszRaster, &nWKBLength));
     932           0 :     GByte *pbyData = pbyDataAutoFreed.get();
     933           0 :     const int nMinimumWKBLength =
     934           0 :         RASTER_HEADER_SIZE + BAND_SIZE(1, nBandDataTypeSize) * nExpectedBands;
     935           0 :     if (nWKBLength < nMinimumWKBLength)
     936             :     {
     937           0 :         CPLDebug("PostGIS_Raster",
     938             :                  "nWKBLength=%d. too short. Expected at least %d", nWKBLength,
     939             :                  nMinimumWKBLength);
     940           0 :         return;
     941             :     }
     942             : 
     943             :     // Do byte-swapping if necessary */
     944           0 :     const bool bIsLittleEndian = (pbyData[0] == 1);
     945             : #ifdef CPL_LSB
     946           0 :     const bool bSwap = !bIsLittleEndian;
     947             : #else
     948             :     const bool bSwap = bIsLittleEndian;
     949             : #endif
     950             : 
     951           0 :     PostGISRasterTileDataset *poRTDS = nullptr;
     952           0 :     if (GetPrimaryKeyRef() != nullptr)
     953           0 :         poRTDS = GetMatchingSourceRef(pszPKID);
     954             :     else
     955           0 :         poRTDS = GetMatchingSourceRef(dfTileUpperLeftX, dfTileUpperLeftY);
     956           0 :     if (poRTDS == nullptr)
     957             :     {
     958           0 :         return;
     959             :     }
     960             : 
     961           0 :     int nCurOffset = RASTER_HEADER_SIZE;
     962           0 :     for (int k = 1; k <= nExpectedBands; k++)
     963             :     {
     964             :         /**
     965             :          * Get the right PostGISRasterRasterBand
     966             :          **/
     967           0 :         int nCurBand = (nExpectedBands > 1) ? k : nBand;
     968             : 
     969             :         /**
     970             :          * Get the right tileband
     971             :          **/
     972           0 :         GDALRasterBand *poRTB = poRTDS->GetRasterBand(nCurBand);
     973           0 :         if (poRTB == nullptr)
     974           0 :             return;
     975             : 
     976             :         // For each band we have at least the flag byte, the nodata value
     977           0 :         if (nWKBLength < nCurOffset + 1 + nBandDataTypeSize)
     978             :         {
     979           0 :             CPLDebug("PostGIS_Raster",
     980             :                      "nWKBLength=%d, not enough data for band %d", nWKBLength,
     981             :                      k);
     982           0 :             return;
     983             :         }
     984             : 
     985             :         // Is it indb-raster ?
     986           0 :         if ((pbyData[nCurOffset] & 0x80) == 0)
     987             :         {
     988           0 :             nCurOffset += 1 + nBandDataTypeSize;
     989           0 :             if (nWKBLength < nCurOffset + nExpectedBandDataSize)
     990             :             {
     991           0 :                 CPLDebug("PostGIS_Raster",
     992             :                          "nWKBLength=%d, not enough data for band %d",
     993             :                          nWKBLength, k);
     994           0 :                 return;
     995             :             }
     996             : 
     997           0 :             GByte *pbyDataToRead = pbyData + nCurOffset;
     998           0 :             nCurOffset += nExpectedBandDataSize;
     999             : 
    1000           0 :             if (bSwap && nBandDataTypeSize > 1)
    1001             :             {
    1002           0 :                 GDALSwapWords(pbyDataToRead, nBandDataTypeSize,
    1003             :                               nTileXSize * nTileYSize, nBandDataTypeSize);
    1004             :             }
    1005             : 
    1006             :             /**
    1007             :              * Manually add each tile data to the cache of the
    1008             :              * matching PostGISRasterTileRasterBand.
    1009             :              **/
    1010           0 :             GDALRasterBlock *poBlock = poRTB->GetLockedBlockRef(0, 0, TRUE);
    1011           0 :             if (poBlock != nullptr)
    1012             :             {
    1013             :                 // Point block data ref to fetched data
    1014           0 :                 memcpy(poBlock->GetDataRef(), pbyDataToRead,
    1015             :                        nExpectedBandDataSize);
    1016             : 
    1017           0 :                 poBlock->DropLock();
    1018             :             }
    1019             :         }
    1020             :         else
    1021             :         {
    1022             :             /**
    1023             :              * Manually add each tile data to the cache of the
    1024             :              * matching PostGISRasterTileRasterBand.
    1025             :              **/
    1026           0 :             GDALRasterBlock *poBlock = poRTB->GetLockedBlockRef(0, 0, TRUE);
    1027           0 :             if (poBlock == nullptr)
    1028           0 :                 return;
    1029           0 :             if (!LoadOutdbRaster(nCurOffset, eDT, k, pbyData, nWKBLength,
    1030             :                                  poBlock->GetDataRef(), dfTileUpperLeftX,
    1031             :                                  dfTileUpperLeftY, dfTileResX, dfTileResY,
    1032             :                                  nTileXSize, nTileYSize))
    1033             :             {
    1034           0 :                 poBlock->DropLock();
    1035           0 :                 return;
    1036             :             }
    1037           0 :             poBlock->DropLock();
    1038             :         }
    1039             :     }
    1040             : 
    1041           0 :     if (nCurOffset != nWKBLength)
    1042             :     {
    1043           0 :         CPLDebug("PostGIS_Raster",
    1044             :                  "Trailing bytes at end of serialized raster");
    1045           0 :         return;
    1046             :     }
    1047             : }
    1048             : 
    1049           0 : bool PostGISRasterDataset::LoadOutdbRaster(int &nCurOffset, GDALDataType eDT,
    1050             :                                            int nBand, const GByte *pbyData,
    1051             :                                            int nWKBLength, void *pImage,
    1052             :                                            double dfTileUpperLeftX,
    1053             :                                            double dfTileUpperLeftY,
    1054             :                                            double dfTileResX, double dfTileResY,
    1055             :                                            int nTileXSize, int nTileYSize)
    1056             : {
    1057           0 :     const int nBandDataTypeSize = GDALGetDataTypeSizeBytes(eDT);
    1058             : 
    1059           0 :     nCurOffset += 1 + nBandDataTypeSize;
    1060           0 :     if (nWKBLength < nCurOffset + 1 + 1)
    1061             :     {
    1062           0 :         CPLDebug("PostGIS_Raster", "nWKBLength=%d, not enough data for band %d",
    1063             :                  nWKBLength, nBand);
    1064           0 :         return false;
    1065             :     }
    1066             :     // Postgis raster outdb band numbering starts at 0
    1067           0 :     GByte nOutdbBandNumber = 1 + pbyData[nCurOffset];
    1068           0 :     nCurOffset++;
    1069           0 :     CPLString osPath;
    1070           0 :     for (int i = 0; nCurOffset + i < nWKBLength; i++)
    1071             :     {
    1072           0 :         if (pbyData[nCurOffset + i] == '\0')
    1073             :         {
    1074           0 :             osPath.assign(reinterpret_cast<const char *>(pbyData) + nCurOffset,
    1075           0 :                           i);
    1076           0 :             nCurOffset += i + 1;
    1077           0 :             break;
    1078             :         }
    1079             :     }
    1080           0 :     if (osPath.empty())
    1081             :     {
    1082           0 :         CPLDebug("PostGIS_Raster",
    1083             :                  "nWKBLength=%d, not enough data for outdb raster band %d",
    1084             :                  nWKBLength, nBand);
    1085           0 :         return false;
    1086             :     }
    1087             : #ifdef DEBUG_VERBOSE
    1088             :     CPLDebug("PostGIS_Raster", "Band %d: GDAL outdb band=%d %s", nBand,
    1089             :              nOutdbBandNumber, osPath.c_str());
    1090             : #endif
    1091           0 :     std::shared_ptr<GDALDataset> poDS;
    1092           0 :     if (!oOutDBDatasetCache.tryGet(osPath, poDS))
    1093             :     {
    1094           0 :         poDS.reset(GDALDataset::Open(osPath, GDAL_OF_RASTER));
    1095           0 :         if (poDS == nullptr)
    1096             :         {
    1097           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s",
    1098             :                      osPath.c_str());
    1099           0 :             return false;
    1100             :         }
    1101           0 :         oOutDBDatasetCache.insert(osPath, poDS);
    1102             :     }
    1103             : 
    1104           0 :     if (nOutdbBandNumber > poDS->GetRasterCount())
    1105             :     {
    1106           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid band number %d for %s",
    1107             :                  nOutdbBandNumber, osPath.c_str());
    1108           0 :         return false;
    1109             :     }
    1110             :     double adfGT[6];
    1111           0 :     poDS->GetGeoTransform(adfGT);
    1112           0 :     int nXOff =
    1113           0 :         static_cast<int>(std::round((dfTileUpperLeftX - adfGT[0]) / adfGT[1]));
    1114           0 :     int nYOff =
    1115           0 :         static_cast<int>(std::round((dfTileUpperLeftY - adfGT[3]) / adfGT[5]));
    1116           0 :     int nXOff2 = static_cast<int>(std::round(
    1117           0 :         (dfTileUpperLeftX + nTileXSize * dfTileResX - adfGT[0]) / adfGT[1]));
    1118           0 :     int nYOff2 = static_cast<int>(std::round(
    1119           0 :         (dfTileUpperLeftY + nTileYSize * dfTileResY - adfGT[3]) / adfGT[5]));
    1120           0 :     int nSrcXSize = nXOff2 - nXOff;
    1121           0 :     int nSrcYSize = nYOff2 - nYOff;
    1122           0 :     if (nXOff < 0 || nYOff < 0 || nXOff2 > poDS->GetRasterXSize() ||
    1123           0 :         nYOff2 > poDS->GetRasterYSize())
    1124             :     {
    1125           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1126             :                  "Requesting (%d,%d,%d,%d) in %dx%d raster", nXOff, nYOff,
    1127             :                  nSrcXSize, nSrcYSize, poDS->GetRasterXSize(),
    1128             :                  poDS->GetRasterYSize());
    1129           0 :         return false;
    1130             :     }
    1131             : 
    1132             :     // Point block data ref to fetched data
    1133             :     return poDS->GetRasterBand(nOutdbBandNumber)
    1134           0 :                ->RasterIO(GF_Read, nXOff, nYOff, nSrcXSize, nSrcYSize, pImage,
    1135             :                           nTileXSize, nTileYSize, eDT, 0, 0,
    1136           0 :                           nullptr) == CE_None;
    1137             : }
    1138             : 
    1139             : /************************************************************************/
    1140             : /*                          LoadSources()                               */
    1141             : /************************************************************************/
    1142             : 
    1143           0 : GBool PostGISRasterDataset::LoadSources(int nXOff, int nYOff, int nXSize,
    1144             :                                         int nYSize, int nBand)
    1145             : {
    1146           0 :     if (!bBuildQuadTreeDynamically)
    1147           0 :         return false;
    1148             : 
    1149           0 :     CPLString osSpatialFilter;
    1150           0 :     CPLString osIDsToFetch;
    1151           0 :     int nYSizeToQuery = nYSize;
    1152             : 
    1153           0 :     bool bFetchAll = false;
    1154           0 :     if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
    1155           0 :         nYSize == nRasterYSize)
    1156             :     {
    1157           0 :         bFetchAll = true;
    1158             :     }
    1159             :     else
    1160             :     {
    1161           0 :         if (nXOff >= m_nLastLoadSourcesXOff &&
    1162           0 :             nYOff >= m_nLastLoadSourcesYOff &&
    1163           0 :             nXOff + nXSize <=
    1164           0 :                 m_nLastLoadSourcesXOff + m_nLastLoadSourcesXSize &&
    1165           0 :             nYOff + nYSize <=
    1166           0 :                 m_nLastLoadSourcesYOff + m_nLastLoadSourcesYSize &&
    1167           0 :             nBand == m_nLastLoadSourcesBand)
    1168             :         {
    1169           0 :             return true;
    1170             :         }
    1171             : 
    1172             :         // To avoid doing too many small requests, try to query for at
    1173             :         // least 10 megapixels.
    1174           0 :         if (nXSize * nYSize < 10 * 1024 * 1024)
    1175             :         {
    1176           0 :             nYSizeToQuery = 10 * 1024 * 1024 / nXSize;
    1177           0 :             nYSizeToQuery = std::min(nYSizeToQuery, nRasterYSize - nYOff);
    1178           0 :             if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
    1179           0 :                 nYSize == nRasterYSize)
    1180             :             {
    1181           0 :                 bFetchAll = true;
    1182             :             }
    1183             :         }
    1184             :     }
    1185             : 
    1186           0 :     if (!bFetchAll)
    1187             :     {
    1188             :         double adfProjWin[8];
    1189           0 :         PolygonFromCoords(nXOff, nYOff, nXOff + nXSize, nYOff + nYSizeToQuery,
    1190             :                           adfProjWin);
    1191             :         osSpatialFilter.Printf("%s && "
    1192             :                         "ST_GeomFromText('POLYGON((%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f %.18f))') ",
    1193             :                         //"AND ST_Intersects(%s, ST_GeomFromEWKT('SRID=%d;POLYGON((%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f %.18f,%.18f %.18f))'))",
    1194             :                         pszColumn,
    1195             :                         adfProjWin[0], adfProjWin[1],
    1196             :                         adfProjWin[2], adfProjWin[3],
    1197             :                         adfProjWin[4], adfProjWin[5],
    1198             :                         adfProjWin[6], adfProjWin[7],
    1199             :                         adfProjWin[0], adfProjWin[1]
    1200             :                         /*,pszColumn, nSrid,
    1201             :                         adfProjWin[0], adfProjWin[1],
    1202             :                         adfProjWin[2], adfProjWin[3],
    1203             :                         adfProjWin[4], adfProjWin[5],
    1204             :                         adfProjWin[6], adfProjWin[7],
    1205           0 :                         adfProjWin[0], adfProjWin[1]*/);
    1206             :     }
    1207             : 
    1208             :     bool bLoadRasters =
    1209           0 :         CPLTestBool(CPLGetConfigOption("PR_FORCE_LOAD_RASTERS", "FALSE"));
    1210           0 :     bool bAllBandCaching = false;
    1211             : 
    1212             :     const std::string osPrimaryKeyNameI(
    1213           0 :         CPLQuotedSQLIdentifier(pszPrimaryKeyName));
    1214           0 :     const std::string osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    1215           0 :     const std::string osTableI(CPLQuotedSQLIdentifier(pszTable));
    1216           0 :     const std::string osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    1217             : 
    1218           0 :     PGresult *poResult = nullptr;
    1219           0 :     if (m_nTiles > 0 && !bFetchAll)
    1220             :     {
    1221           0 :         CPLString osCommand;
    1222             :         osCommand.Printf("SELECT %s FROM %s.%s", osPrimaryKeyNameI.c_str(),
    1223           0 :                          osSchemaI.c_str(), osTableI.c_str());
    1224           0 :         osCommand += " WHERE ";
    1225           0 :         osCommand += osSpatialFilter;
    1226             : 
    1227           0 :         osSpatialFilter = "";
    1228             : 
    1229           0 :         poResult = PQexec(poConn, osCommand.c_str());
    1230             : 
    1231             : #ifdef DEBUG_QUERY
    1232             :         CPLDebug("PostGIS_Raster",
    1233             :                  "PostGISRasterDataset::LoadSources(): Query = \"%s\" --> "
    1234             :                  "number of rows = %d",
    1235             :                  osCommand.c_str(), poResult ? PQntuples(poResult) : 0);
    1236             : #endif
    1237             : 
    1238           0 :         if (poResult == nullptr ||
    1239           0 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    1240           0 :             PQntuples(poResult) < 0)
    1241             :         {
    1242             : 
    1243           0 :             if (poResult)
    1244           0 :                 PQclear(poResult);
    1245             : 
    1246           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1247             :                      "PostGISRasterDataset::LoadSources(): %s",
    1248           0 :                      PQerrorMessage(poConn));
    1249             : 
    1250           0 :             return false;
    1251             :         }
    1252             : 
    1253           0 :         if (bTilesSameDimension && nBand > 0)
    1254             :         {
    1255             :             GIntBig nMemoryRequiredForTiles =
    1256           0 :                 static_cast<GIntBig>(PQntuples(poResult)) * nTileWidth *
    1257           0 :                 nTileHeight *
    1258           0 :                 (GDALGetDataTypeSize(
    1259           0 :                      GetRasterBand(nBand)->GetRasterDataType()) /
    1260           0 :                  8);
    1261           0 :             GIntBig nCacheMax = GDALGetCacheMax64();
    1262           0 :             if (nBands * nMemoryRequiredForTiles <= nCacheMax)
    1263             :             {
    1264           0 :                 bLoadRasters = true;
    1265           0 :                 bAllBandCaching = true;
    1266             :             }
    1267           0 :             else if (nMemoryRequiredForTiles <= nCacheMax)
    1268             :             {
    1269           0 :                 bLoadRasters = true;
    1270             :             }
    1271             :         }
    1272             : 
    1273             :         int i;
    1274           0 :         for (i = 0; i < PQntuples(poResult); i++)
    1275             :         {
    1276           0 :             const char *pszPKID = PQgetvalue(poResult, i, 0);
    1277           0 :             PostGISRasterTileDataset *poTile = GetMatchingSourceRef(pszPKID);
    1278           0 :             int bFetchTile = FALSE;
    1279           0 :             if (poTile == nullptr)
    1280           0 :                 bFetchTile = TRUE;
    1281           0 :             else if (bLoadRasters)
    1282             :             {
    1283             :                 PostGISRasterTileRasterBand *poTileBand =
    1284           0 :                     cpl::down_cast<PostGISRasterTileRasterBand *>(
    1285             :                         poTile->GetRasterBand(nBand));
    1286           0 :                 if (!poTileBand->IsCached())
    1287           0 :                     bFetchTile = TRUE;
    1288             :             }
    1289           0 :             if (bFetchTile)
    1290             :             {
    1291           0 :                 if (!osIDsToFetch.empty())
    1292           0 :                     osIDsToFetch += ",";
    1293           0 :                 osIDsToFetch += "'";
    1294           0 :                 osIDsToFetch += pszPKID;
    1295           0 :                 osIDsToFetch += "'";
    1296             :             }
    1297             :         }
    1298             : 
    1299           0 :         PQclear(poResult);
    1300             :     }
    1301             : 
    1302           0 :     if (bFetchAll || !osIDsToFetch.empty() || !osSpatialFilter.empty())
    1303             :     {
    1304           0 :         std::string osWHERE;
    1305           0 :         if (!osIDsToFetch.empty())
    1306             :         {
    1307           0 :             osWHERE += osPrimaryKeyNameI;
    1308           0 :             osWHERE += " IN (";
    1309           0 :             osWHERE += osIDsToFetch;
    1310           0 :             osWHERE += ")";
    1311             :         }
    1312           0 :         else if (!osSpatialFilter.empty())
    1313             :         {
    1314           0 :             osWHERE = std::move(osSpatialFilter);
    1315             :         }
    1316           0 :         if (pszWhere != nullptr)
    1317             :         {
    1318           0 :             if (!osWHERE.empty())
    1319           0 :                 osWHERE += " AND ";
    1320           0 :             osWHERE += "(";
    1321           0 :             osWHERE += pszWhere;
    1322           0 :             osWHERE += ")";
    1323             :         }
    1324             : 
    1325           0 :         bool bCanUseClientSide = true;
    1326           0 :         if (bLoadRasters &&
    1327           0 :             eOutDBResolution == OutDBResolution::CLIENT_SIDE_IF_POSSIBLE)
    1328             :         {
    1329             :             bCanUseClientSide =
    1330           0 :                 CanUseClientSideOutDB(bAllBandCaching, nBand, osWHERE);
    1331             :         }
    1332             : 
    1333           0 :         CPLString osCommand;
    1334             :         osCommand.Printf("SELECT %s, ST_Metadata(%s)",
    1335           0 :                          osPrimaryKeyNameI.c_str(), osColumnI.c_str());
    1336           0 :         if (bLoadRasters)
    1337             :         {
    1338           0 :             CPLString orRasterToFetch;
    1339           0 :             if (bAllBandCaching)
    1340             :             {
    1341           0 :                 orRasterToFetch = osColumnI;
    1342             :             }
    1343             :             else
    1344             :             {
    1345             :                 orRasterToFetch.Printf("ST_Band(%s, %d)", osColumnI.c_str(),
    1346           0 :                                        nBand);
    1347             :             }
    1348           0 :             if (eOutDBResolution == OutDBResolution::SERVER_SIDE ||
    1349           0 :                 !bCanUseClientSide)
    1350             :             {
    1351             :                 orRasterToFetch =
    1352           0 :                     "encode(ST_AsBinary(" + orRasterToFetch + ",TRUE),'hex')";
    1353             :             }
    1354           0 :             osCommand += ", " + orRasterToFetch;
    1355             :         }
    1356             :         osCommand +=
    1357           0 :             CPLSPrintf(" FROM %s.%s", osSchemaI.c_str(), osTableI.c_str());
    1358           0 :         if (!osWHERE.empty())
    1359             :         {
    1360           0 :             osCommand += " WHERE ";
    1361           0 :             osCommand += osWHERE;
    1362             :         }
    1363             : 
    1364           0 :         poResult = PQexec(poConn, osCommand.c_str());
    1365             : 
    1366             : #ifdef DEBUG_QUERY
    1367             :         CPLDebug("PostGIS_Raster",
    1368             :                  "PostGISRasterDataset::LoadSources(): Query = \"%s\" --> "
    1369             :                  "number of rows = %d",
    1370             :                  osCommand.c_str(), poResult ? PQntuples(poResult) : 0);
    1371             : #endif
    1372             : 
    1373           0 :         if (poResult == nullptr ||
    1374           0 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    1375           0 :             PQntuples(poResult) < 0)
    1376             :         {
    1377             : 
    1378           0 :             if (poResult)
    1379           0 :                 PQclear(poResult);
    1380             : 
    1381           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1382             :                      "PostGISRasterDataset::LoadSources(): %s",
    1383           0 :                      PQerrorMessage(poConn));
    1384             : 
    1385           0 :             return false;
    1386             :         }
    1387             : 
    1388           0 :         for (int i = 0; i < PQntuples(poResult); i++)
    1389             :         {
    1390           0 :             const char *pszPKID = PQgetvalue(poResult, i, 0);
    1391           0 :             const char *pszMetadata = PQgetvalue(poResult, i, 1);
    1392             : 
    1393           0 :             PostGISRasterTileDataset *poRTDS = GetMatchingSourceRef(pszPKID);
    1394           0 :             if (poRTDS == nullptr)
    1395             :             {
    1396           0 :                 poRTDS = BuildRasterTileDataset(pszMetadata, pszPKID,
    1397             :                                                 GetRasterCount(), nullptr);
    1398           0 :                 if (poRTDS != nullptr)
    1399             :                 {
    1400           0 :                     AddComplexSource(poRTDS);
    1401             : 
    1402           0 :                     oMapPKIDToRTDS[poRTDS->pszPKID] = poRTDS;
    1403           0 :                     papoSourcesHolders =
    1404             :                         static_cast<PostGISRasterTileDataset **>(
    1405           0 :                             CPLRealloc(papoSourcesHolders,
    1406             :                                        sizeof(PostGISRasterTileDataset *) *
    1407           0 :                                            (m_nTiles + 1)));
    1408           0 :                     papoSourcesHolders[m_nTiles++] = poRTDS;
    1409           0 :                     CPLQuadTreeInsert(hQuadTree, poRTDS);
    1410             :                 }
    1411             :             }
    1412             : 
    1413           0 :             if (bLoadRasters && poRTDS != nullptr)
    1414             :             {
    1415           0 :                 const char *pszRaster = PQgetvalue(poResult, i, 2);
    1416           0 :                 CacheTile(pszMetadata, pszRaster, pszPKID, nBand,
    1417             :                           bAllBandCaching);
    1418             :             }
    1419             :         }
    1420             : 
    1421           0 :         PQclear(poResult);
    1422             :     }
    1423             : 
    1424             :     // If we have fetched the surface of all the dataset, then all sources have
    1425             :     // been built, and we don't need to do a spatial query on following
    1426             :     // IRasterIO() calls
    1427           0 :     if (bFetchAll)
    1428           0 :         bBuildQuadTreeDynamically = false;
    1429             : 
    1430           0 :     m_nLastLoadSourcesXOff = nXOff;
    1431           0 :     m_nLastLoadSourcesYOff = nYOff;
    1432           0 :     m_nLastLoadSourcesXSize = nXSize;
    1433           0 :     m_nLastLoadSourcesYSize = nYSizeToQuery;
    1434           0 :     m_nLastLoadSourcesBand = nBand;
    1435             : 
    1436           0 :     return true;
    1437             : }
    1438             : 
    1439             : /***********************************************************************
    1440             :  * \brief Determine if the tiles satisfying a request use outdb rasters
    1441             :  *        that can be resolved client-side.
    1442             :  **********************************************************************/
    1443           0 : bool PostGISRasterDataset::CanUseClientSideOutDB(bool bAllBandCaching,
    1444             :                                                  int nBand,
    1445             :                                                  const CPLString &osWHERE)
    1446             : {
    1447           0 :     CPLString osCommand;
    1448           0 :     CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    1449           0 :     CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
    1450           0 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    1451             : 
    1452           0 :     if (bAllBandCaching)
    1453             :     {
    1454           0 :         if (bHasStBandFileSize)
    1455             :         {
    1456             :             osCommand.Printf(
    1457             :                 "SELECT DISTINCT ST_BandPath(%s,band), "
    1458             :                 "ST_BandFileSize(%s,band), ST_BandFileTimeStamp(%s,band) FROM "
    1459             :                 "(SELECT %s, generate_series(1, ST_NumBands(%s)) band FROM "
    1460             :                 "%s.%s%s) foo",
    1461             :                 osColumnI.c_str(), osColumnI.c_str(), osColumnI.c_str(),
    1462             :                 osColumnI.c_str(), osColumnI.c_str(), osSchemaI.c_str(),
    1463             :                 osTableI.c_str(),
    1464           0 :                 !osWHERE.empty() ? (" WHERE " + osWHERE).c_str() : "");
    1465             :         }
    1466             :         else
    1467             :         {
    1468             :             osCommand.Printf(
    1469             :                 "SELECT DISTINCT ST_BandPath(%s,band) FROM "
    1470             :                 "(SELECT %s, generate_series(1, ST_NumBands(%s)) band FROM "
    1471             :                 "%s.%s%s) foo",
    1472             :                 osColumnI.c_str(), osColumnI.c_str(), osColumnI.c_str(),
    1473             :                 osSchemaI.c_str(), osTableI.c_str(),
    1474           0 :                 !osWHERE.empty() ? (" WHERE " + osWHERE).c_str() : "");
    1475             :         }
    1476             :     }
    1477             :     else
    1478             :     {
    1479           0 :         if (bHasStBandFileSize)
    1480             :         {
    1481             :             osCommand.Printf(
    1482             :                 "SELECT DISTINCT ST_BandPath(%s,%d), "
    1483             :                 "ST_BandFileSize(%s,%d), ST_BandFileTimeStamp(%s,%d) "
    1484             :                 "FROM %s.%s%s",
    1485             :                 osColumnI.c_str(), nBand, osColumnI.c_str(), nBand,
    1486             :                 osColumnI.c_str(), nBand, osSchemaI.c_str(), osTableI.c_str(),
    1487           0 :                 !osWHERE.empty() ? (" WHERE " + osWHERE).c_str() : "");
    1488             :         }
    1489             :         else
    1490             :         {
    1491             :             osCommand.Printf(
    1492             :                 "SELECT DISTINCT ST_BandPath(%s,%d) FROM %s.%s%s",
    1493             :                 osColumnI.c_str(), nBand, osSchemaI.c_str(), osTableI.c_str(),
    1494           0 :                 !osWHERE.empty() ? (" WHERE " + osWHERE).c_str() : "");
    1495             :         }
    1496             :     }
    1497           0 :     PGresult *poResult = PQexec(poConn, osCommand.c_str());
    1498             : #ifdef DEBUG_QUERY
    1499             :     CPLDebug("PostGIS_Raster",
    1500             :              "PostGISRasterRasterBand::CanUseClientSideOutDB(): "
    1501             :              "Query = \"%s\" --> number of rows = %d",
    1502             :              osCommand.c_str(), poResult ? PQntuples(poResult) : 0);
    1503             : #endif
    1504             : 
    1505           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    1506           0 :         PQntuples(poResult) < 0)
    1507             :     {
    1508             : 
    1509           0 :         if (poResult)
    1510           0 :             PQclear(poResult);
    1511             : 
    1512           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1513             :                  "PostGISRasterRasterBand::CanUseClientSideOutDB(): %s",
    1514           0 :                  PQerrorMessage(poConn));
    1515             : 
    1516           0 :         return false;
    1517             :     }
    1518           0 :     bool bCanUseClientSide = true;
    1519           0 :     const int nTuples = PQntuples(poResult);
    1520           0 :     for (int i = 0; i < nTuples; i++)
    1521             :     {
    1522           0 :         const char *pszFilename = PQgetvalue(poResult, i, 0);
    1523           0 :         if (pszFilename)
    1524             :         {
    1525           0 :             bool bUsable = false;
    1526           0 :             if (!oOutDBFilenameUsable.tryGet(std::string(pszFilename), bUsable))
    1527             :             {
    1528             :                 VSIStatBufL sStat;
    1529           0 :                 bUsable = (VSIStatL(pszFilename, &sStat) == 0);
    1530           0 :                 if (bUsable && bHasStBandFileSize)
    1531             :                 {
    1532           0 :                     const char *pszSize = PQgetvalue(poResult, i, 1);
    1533           0 :                     const char *pszTimestamp = PQgetvalue(poResult, i, 2);
    1534           0 :                     if (pszSize && pszTimestamp)
    1535             :                     {
    1536           0 :                         bUsable &=
    1537           0 :                             (static_cast<GUInt64>(CPLAtoGIntBig(pszSize)) ==
    1538           0 :                              static_cast<GUInt64>(sStat.st_size));
    1539           0 :                         bUsable &= (static_cast<GUInt64>(
    1540           0 :                                         CPLAtoGIntBig(pszTimestamp)) ==
    1541           0 :                                     static_cast<GUInt64>(sStat.st_mtime));
    1542             :                     }
    1543             :                 }
    1544           0 :                 oOutDBFilenameUsable.insert(std::string(pszFilename), bUsable);
    1545             :             }
    1546           0 :             if (!bUsable)
    1547             :             {
    1548           0 :                 CPLDebug("PostGIS_Raster",
    1549             :                          "File %s not usable from client side", pszFilename);
    1550           0 :                 bCanUseClientSide = false;
    1551             :             }
    1552             :         }
    1553             :     }
    1554           0 :     PQclear(poResult);
    1555           0 :     return bCanUseClientSide;
    1556             : }
    1557             : 
    1558             : /***********************************************************************
    1559             :  * \brief Get some useful metadata for all bands
    1560             :  *
    1561             :  * The allocated memory is responsibility of the caller
    1562             :  **********************************************************************/
    1563           0 : BandMetadata *PostGISRasterDataset::GetBandsMetadata(int *pnBands)
    1564             : {
    1565           0 :     BandMetadata *poBMD = nullptr;
    1566           0 :     PGresult *poResult = nullptr;
    1567           0 :     CPLString osCommand;
    1568           0 :     char *pszRes = nullptr;
    1569           0 :     char *pszFilteredRes = nullptr;
    1570           0 :     char **papszParams = nullptr;
    1571             : 
    1572           0 :     CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    1573           0 :     CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
    1574           0 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    1575             : 
    1576             :     osCommand.Printf("select st_bandmetadata(%s, band) from "
    1577             :                      "(select %s, generate_series(1, %d) band from "
    1578             :                      "(select %s from %s.%s where (%s) AND st_numbands(%s)=%d "
    1579             :                      "limit 1) bar) foo",
    1580             :                      osColumnI.c_str(), osColumnI.c_str(), nBandsToCreate,
    1581             :                      osColumnI.c_str(), osSchemaI.c_str(), osTableI.c_str(),
    1582           0 :                      pszWhere ? pszWhere : "true", osColumnI.c_str(),
    1583           0 :                      nBandsToCreate);
    1584             : 
    1585             : #ifdef DEBUG_QUERY
    1586             :     CPLDebug("PostGIS_Raster",
    1587             :              "PostGISRasterDataset::GetBandsMetadata(): Query: %s",
    1588             :              osCommand.c_str());
    1589             : #endif
    1590             : 
    1591           0 :     poResult = PQexec(poConn, osCommand.c_str());
    1592             :     /* Error getting info from database */
    1593           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    1594           0 :         PQntuples(poResult) <= 0)
    1595             :     {
    1596             : 
    1597           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    1598             :                     "Error getting band metadata while creating raster "
    1599             :                     "bands");
    1600             : 
    1601           0 :         CPLDebug("PostGIS_Raster",
    1602             :                  "PostGISRasterDataset::GetBandsMetadata(): %s",
    1603           0 :                  PQerrorMessage(poConn));
    1604             : 
    1605           0 :         if (poResult)
    1606           0 :             PQclear(poResult);
    1607             : 
    1608           0 :         return nullptr;
    1609             :     }
    1610             : 
    1611             :     // Matches nBands
    1612           0 :     int nTuples = PQntuples(poResult);
    1613             : 
    1614             :     poBMD = static_cast<BandMetadata *>(
    1615           0 :         VSI_MALLOC2_VERBOSE(nTuples, sizeof(BandMetadata)));
    1616           0 :     if (poBMD == nullptr)
    1617             :     {
    1618           0 :         PQclear(poResult);
    1619             : 
    1620           0 :         return nullptr;
    1621             :     }
    1622             : 
    1623           0 :     int iBand = 0;
    1624             : 
    1625           0 :     for (iBand = 0; iBand < nTuples; iBand++)
    1626             :     {
    1627             : 
    1628             :         // Get metadata record
    1629           0 :         pszRes = CPLStrdup(PQgetvalue(poResult, iBand, 0));
    1630             : 
    1631             :         // Skip first "("
    1632           0 :         pszFilteredRes = pszRes + 1;
    1633             : 
    1634             :         // Skip last ")"
    1635           0 :         pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
    1636             : 
    1637             :         // Tokenize
    1638           0 :         papszParams = CSLTokenizeString2(
    1639             :             pszFilteredRes, ",", CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS);
    1640           0 :         CPLAssert(CSLCount(papszParams) >= ELEMENTS_OF_BAND_METADATA_RECORD);
    1641             : 
    1642           0 :         CPLFree(pszRes);
    1643             : 
    1644             :         // If the band doesn't have nodata, NULL is returned as nodata
    1645           0 :         TranslateDataType(papszParams[POS_PIXELTYPE], &(poBMD[iBand].eDataType),
    1646           0 :                           &(poBMD[iBand].nBitsDepth));
    1647             : 
    1648           0 :         if (papszParams[POS_NODATAVALUE] == nullptr ||
    1649           0 :             EQUAL(papszParams[POS_NODATAVALUE], "NULL") ||
    1650           0 :             EQUAL(papszParams[POS_NODATAVALUE], "f") ||
    1651           0 :             EQUAL(papszParams[POS_NODATAVALUE], ""))
    1652             :         {
    1653             : 
    1654           0 :             poBMD[iBand].bHasNoDataValue = false;
    1655           0 :             poBMD[iBand].dfNoDataValue = CPLAtof(NO_VALID_RES);
    1656             :         }
    1657             : 
    1658             :         else
    1659             :         {
    1660           0 :             poBMD[iBand].bHasNoDataValue = true;
    1661           0 :             poBMD[iBand].dfNoDataValue = CPLAtof(papszParams[POS_NODATAVALUE]);
    1662             :         }
    1663             : 
    1664           0 :         poBMD[iBand].bIsOffline = (papszParams[POS_ISOUTDB] != nullptr)
    1665           0 :                                       ? EQUAL(papszParams[POS_ISOUTDB], "t")
    1666             :                                       : false;
    1667             : 
    1668           0 :         CSLDestroy(papszParams);
    1669             :     }
    1670             : 
    1671           0 :     if (pnBands)
    1672           0 :         *pnBands = nTuples;
    1673             : 
    1674           0 :     PQclear(poResult);
    1675             : 
    1676           0 :     return poBMD;
    1677             : }
    1678             : 
    1679             : /***********************************************************************
    1680             :  * \brief Function to get the bounding box of each element inserted in
    1681             :  * the QuadTree index
    1682             :  **********************************************************************/
    1683           0 : static void GetTileBoundingBox(const void *hFeature, CPLRectObj *pBounds)
    1684             : {
    1685           0 :     PostGISRasterTileDataset *poRTD = const_cast<PostGISRasterTileDataset *>(
    1686             :         reinterpret_cast<const PostGISRasterTileDataset *>(hFeature));
    1687             : 
    1688             :     double adfTileGeoTransform[6];
    1689           0 :     poRTD->GetGeoTransform(adfTileGeoTransform);
    1690             : 
    1691           0 :     int nTileWidth = poRTD->GetRasterXSize();
    1692           0 :     int nTileHeight = poRTD->GetRasterYSize();
    1693             : 
    1694           0 :     pBounds->minx = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_X];
    1695           0 :     pBounds->maxx = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_X] +
    1696           0 :                     nTileWidth * adfTileGeoTransform[GEOTRSFRM_WE_RES];
    1697             : 
    1698           0 :     if (adfTileGeoTransform[GEOTRSFRM_NS_RES] >= 0.0)
    1699             :     {
    1700           0 :         pBounds->miny = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y];
    1701           0 :         pBounds->maxy = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    1702           0 :                         nTileHeight * adfTileGeoTransform[GEOTRSFRM_NS_RES];
    1703             :     }
    1704             :     else
    1705             :     {
    1706           0 :         pBounds->maxy = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y];
    1707           0 :         pBounds->miny = adfTileGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    1708           0 :                         nTileHeight * adfTileGeoTransform[GEOTRSFRM_NS_RES];
    1709             :     }
    1710             : 
    1711             : #ifdef DEBUG_VERBOSE
    1712             :     CPLDebug("PostGIS_Raster",
    1713             :              "TileBoundingBox minx=%f miny=%f maxx=%f maxy=%f "
    1714             :              "adfTileGeoTransform[GEOTRSFRM_NS_RES]=%f",
    1715             :              pBounds->minx, pBounds->miny, pBounds->maxx, pBounds->maxy,
    1716             :              adfTileGeoTransform[GEOTRSFRM_NS_RES]);
    1717             : #endif
    1718             : 
    1719           0 :     return;
    1720             : }
    1721             : 
    1722             : /********************************************************
    1723             :  * \brief Builds a PostGISRasterTileDataset* object from the ST_Metadata
    1724             :  ********************************************************/
    1725           0 : PostGISRasterTileDataset *PostGISRasterDataset::BuildRasterTileDataset(
    1726             :     const char *pszMetadata, const char *pszPKID, int nBandsFetched,
    1727             :     BandMetadata *poBandMetaData)
    1728             : {
    1729             :     // Get metadata record
    1730           0 :     char *pszRes = CPLStrdup(pszMetadata);
    1731             : 
    1732             :     // Skip first "("
    1733           0 :     char *pszFilteredRes = pszRes + 1;
    1734             : 
    1735             :     // Skip last ")"
    1736           0 :     pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
    1737             : 
    1738             :     // Tokenize
    1739           0 :     char **papszParams = CSLTokenizeString2(
    1740             :         pszFilteredRes, ",", CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS);
    1741           0 :     CPLAssert(CSLCount(papszParams) >= ELEMENTS_OF_METADATA_RECORD);
    1742             : 
    1743           0 :     CPLFree(pszRes);
    1744             : 
    1745           0 :     double tileSkewX = CPLAtof(papszParams[POS_SKEWX]);
    1746           0 :     double tileSkewY = CPLAtof(papszParams[POS_SKEWY]);
    1747             : 
    1748             :     // Rotated rasters are not allowed, so far
    1749             :     // TODO: allow them
    1750           0 :     if (!CPLIsEqual(tileSkewX, 0.0) || !CPLIsEqual(tileSkewY, 0.0))
    1751             :     {
    1752             : 
    1753           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    1754             :                     "GDAL PostGIS Raster driver can not work with "
    1755             :                     "rotated rasters yet.");
    1756             : 
    1757           0 :         CSLDestroy(papszParams);
    1758           0 :         return nullptr;
    1759             :     }
    1760             : 
    1761           0 :     int l_nTileWidth = atoi(papszParams[POS_WIDTH]);
    1762           0 :     int l_nTileHeight = atoi(papszParams[POS_HEIGHT]);
    1763             : 
    1764             :     /**
    1765             :      * Now, construct a PostGISRasterTileDataset, and add
    1766             :      * its bands as sources for the general raster bands
    1767             :      **/
    1768           0 :     int nTileBands = atoi(papszParams[POS_NBANDS]);
    1769             : 
    1770             :     /**
    1771             :      * If the source doesn't have the same number of bands than
    1772             :      * the raster band, is discarded
    1773             :      **/
    1774           0 :     if (nTileBands != nBandsFetched)
    1775             :     {
    1776           0 :         CPLDebug("PostGIS_Raster",
    1777             :                  "PostGISRasterDataset::"
    1778             :                  "BuildRasterTileDataset(): Tile has %d "
    1779             :                  "bands, and the raster has %d bands. Discarding "
    1780             :                  "this tile",
    1781             :                  nTileBands, nBandsFetched);
    1782             : 
    1783           0 :         CSLDestroy(papszParams);
    1784             : 
    1785           0 :         return nullptr;
    1786             :     }
    1787             : 
    1788             :     PostGISRasterTileDataset *poRTDS =
    1789           0 :         new PostGISRasterTileDataset(this, l_nTileWidth, l_nTileHeight);
    1790           0 :     poRTDS->ShareLockWithParentDataset(this);
    1791             : 
    1792           0 :     if (GetPrimaryKeyRef() != nullptr)
    1793             :     {
    1794           0 :         poRTDS->pszPKID = CPLStrdup(pszPKID);
    1795             :     }
    1796             : 
    1797           0 :     poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_X] =
    1798           0 :         CPLAtof(papszParams[POS_UPPERLEFTX]);
    1799             : 
    1800           0 :     poRTDS->adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] =
    1801           0 :         CPLAtof(papszParams[POS_UPPERLEFTY]);
    1802             : 
    1803           0 :     poRTDS->adfGeoTransform[GEOTRSFRM_WE_RES] =
    1804           0 :         CPLAtof(papszParams[POS_SCALEX]);
    1805             : 
    1806           0 :     poRTDS->adfGeoTransform[GEOTRSFRM_NS_RES] =
    1807           0 :         CPLAtof(papszParams[POS_SCALEY]);
    1808             : 
    1809           0 :     for (int j = 0; j < nTileBands; j++)
    1810             :     {
    1811             : 
    1812             :         // Create band
    1813           0 :         poRTDS->SetBand(j + 1,
    1814             :                         new PostGISRasterTileRasterBand(
    1815             :                             poRTDS, j + 1,
    1816             :                             (poBandMetaData)
    1817           0 :                                 ? poBandMetaData[j].eDataType
    1818           0 :                                 : GetRasterBand(j + 1)->GetRasterDataType()));
    1819             :     }
    1820             : 
    1821           0 :     CSLDestroy(papszParams);
    1822             : 
    1823           0 :     return poRTDS;
    1824             : }
    1825             : 
    1826             : /********************************************************
    1827             :  * \brief Updates components GEOTRSFRM_WE_RES and GEOTRSFRM_NS_RES
    1828             :  *        of dataset adfGeoTransform
    1829             :  ********************************************************/
    1830           0 : void PostGISRasterDataset::UpdateGlobalResolutionWithTileResolution(
    1831             :     double tilePixelSizeX, double tilePixelSizeY)
    1832             : {
    1833             :     // Calculate pixel size
    1834           0 :     if (resolutionStrategy == AVERAGE_RESOLUTION ||
    1835           0 :         resolutionStrategy == AVERAGE_APPROX_RESOLUTION)
    1836             :     {
    1837           0 :         adfGeoTransform[GEOTRSFRM_WE_RES] += tilePixelSizeX;
    1838           0 :         adfGeoTransform[GEOTRSFRM_NS_RES] += tilePixelSizeY;
    1839             :     }
    1840             : 
    1841           0 :     else if (resolutionStrategy == HIGHEST_RESOLUTION)
    1842             :     {
    1843           0 :         adfGeoTransform[GEOTRSFRM_WE_RES] =
    1844           0 :             std::min(adfGeoTransform[GEOTRSFRM_WE_RES], tilePixelSizeX);
    1845             : 
    1846             :         /**
    1847             :          * Yes : as ns_res is negative, the highest resolution
    1848             :          * is the max value.
    1849             :          *
    1850             :          * Negative tilePixelSizeY means that the coords origin
    1851             :          * is in top left corner. This is not the common
    1852             :          * situation. Most image files store data from top to
    1853             :          * bottom, while the projected coordinate systems
    1854             :          * utilize traditional Cartesian coordinates with the
    1855             :          * origin in the conventional lower-left corner (bottom
    1856             :          * to top). For that reason, this parameter is normally
    1857             :          * negative.
    1858             :          **/
    1859           0 :         if (tilePixelSizeY < 0.0)
    1860           0 :             adfGeoTransform[GEOTRSFRM_NS_RES] =
    1861           0 :                 std::max(adfGeoTransform[GEOTRSFRM_NS_RES], tilePixelSizeY);
    1862             :         else
    1863           0 :             adfGeoTransform[GEOTRSFRM_NS_RES] =
    1864           0 :                 std::min(adfGeoTransform[GEOTRSFRM_NS_RES], tilePixelSizeY);
    1865             :     }
    1866             : 
    1867           0 :     else if (resolutionStrategy == LOWEST_RESOLUTION)
    1868             :     {
    1869           0 :         adfGeoTransform[GEOTRSFRM_WE_RES] =
    1870           0 :             std::max(adfGeoTransform[GEOTRSFRM_WE_RES], tilePixelSizeX);
    1871             : 
    1872           0 :         if (tilePixelSizeY < 0.0)
    1873           0 :             adfGeoTransform[GEOTRSFRM_NS_RES] =
    1874           0 :                 std::min(adfGeoTransform[GEOTRSFRM_NS_RES], tilePixelSizeY);
    1875             :         else
    1876           0 :             adfGeoTransform[GEOTRSFRM_NS_RES] =
    1877           0 :                 std::max(adfGeoTransform[GEOTRSFRM_NS_RES], tilePixelSizeY);
    1878             :     }
    1879           0 : }
    1880             : 
    1881             : /***********************************************************************
    1882             :  * \brief Build bands
    1883             :  ***********************************************************************/
    1884           0 : void PostGISRasterDataset::BuildBands(BandMetadata *poBandMetaData,
    1885             :                                       int nBandsFetched)
    1886             : {
    1887             : #ifdef DEBUG_VERBOSE
    1888             :     CPLDebug("PostGIS_Raster",
    1889             :              "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    1890             :              "Now constructing the raster dataset bands");
    1891             : #endif
    1892             : 
    1893             :     int iBand;
    1894           0 :     for (iBand = 0; iBand < nBandsFetched; iBand++)
    1895             :     {
    1896             : 
    1897           0 :         SetBand(iBand + 1, new PostGISRasterRasterBand(
    1898           0 :                                this, iBand + 1, poBandMetaData[iBand].eDataType,
    1899           0 :                                poBandMetaData[iBand].bHasNoDataValue,
    1900           0 :                                poBandMetaData[iBand].dfNoDataValue));
    1901             : 
    1902             :         // Set some band metadata items
    1903           0 :         GDALRasterBand *b = GetRasterBand(iBand + 1);
    1904           0 :         if (poBandMetaData[iBand].nBitsDepth < 8)
    1905             :         {
    1906           0 :             b->SetMetadataItem(
    1907             :                 "NBITS",
    1908           0 :                 CPLString().Printf("%d", poBandMetaData[iBand].nBitsDepth),
    1909           0 :                 "IMAGE_STRUCTURE");
    1910             :         }
    1911             : 
    1912             : #ifdef DEBUG_VERBOSE
    1913             :         CPLDebug("PostGIS_Raster",
    1914             :                  "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    1915             :                  "Band %d built",
    1916             :                  iBand + 1);
    1917             : #endif
    1918             :     }
    1919           0 : }
    1920             : 
    1921             : /***********************************************************************
    1922             :  * \brief Construct just one dataset from all the results fetched.
    1923             :  *
    1924             :  * This method is not very elegant. It is strongly attached to
    1925             :  * SetRasterProperties (it assumes poResult is not NULL, and the actual
    1926             :  * results are stored at fixed positions). I just did it to avoid a
    1927             :  * huge SetRasterProperties method.
    1928             :  *
    1929             :  * I know, this could be avoided in a better way. Like implementing a
    1930             :  * wrapper to raise queries and get results without all the checking
    1931             :  * overhead. I'd like to do it, someday...
    1932             :  **********************************************************************/
    1933           0 : GBool PostGISRasterDataset::ConstructOneDatasetFromTiles(PGresult *poResult)
    1934             : {
    1935             : 
    1936             :     /*******************************************************************
    1937             :      * We first get the band metadata. So we'll can use it as metadata
    1938             :      * for all the sources.
    1939             :      *
    1940             :      * We just fetch the band metadata from 1 tile. So, we assume that:
    1941             :      * - All the bands have the same data type
    1942             :      * - All the bands have the same NODATA value
    1943             :      *
    1944             :      * It is user's responsibility to ensure the requested table fit in
    1945             :      * this schema. He/she may use the 'where' clause to ensure this
    1946             :      ******************************************************************/
    1947           0 :     int nBandsFetched = 0;
    1948           0 :     BandMetadata *poBandMetaData = GetBandsMetadata(&nBandsFetched);
    1949             : 
    1950             :     /*******************************************************************
    1951             :      * Now, we can iterate over the input query's results (metadata
    1952             :      * from all the database tiles).
    1953             :      *
    1954             :      * In this iteration, we will construct the dataset GeoTransform
    1955             :      * array and we will add each tile's band as source for each of our
    1956             :      * rasterbands.
    1957             :      ******************************************************************/
    1958           0 :     int l_nTiles = PQntuples(poResult);
    1959             : 
    1960           0 :     adfGeoTransform[GEOTRSFRM_TOPLEFT_X] = xmin;
    1961             : 
    1962           0 :     int nField = (GetPrimaryKeyRef() != nullptr) ? 1 : 0;
    1963             : 
    1964             :     /**
    1965             :      * Construct the dataset from metadata of all tiles,
    1966             :      * and create PostGISRasterTileDataset objects, to hold the
    1967             :      * PostGISRasterTileRasterBands objects that will be used as sources
    1968             :      **/
    1969             : 
    1970             :     int i;
    1971             : 
    1972             : #ifdef DEBUG_VERBOSE
    1973             :     CPLDebug("PostGIS_Raster",
    1974             :              "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    1975             :              "Constructing one dataset from %d tiles",
    1976             :              l_nTiles);
    1977             : #endif
    1978             : 
    1979           0 :     papoSourcesHolders = static_cast<PostGISRasterTileDataset **>(
    1980           0 :         VSI_CALLOC_VERBOSE(l_nTiles, sizeof(PostGISRasterTileDataset *)));
    1981             : 
    1982           0 :     if (papoSourcesHolders == nullptr)
    1983             :     {
    1984           0 :         VSIFree(poBandMetaData);
    1985             : 
    1986           0 :         return false;
    1987             :     }
    1988             : 
    1989           0 :     int nValidTiles = 0;
    1990           0 :     for (i = 0; i < l_nTiles; i++)
    1991             :     {
    1992           0 :         PostGISRasterTileDataset *poRTDS = BuildRasterTileDataset(
    1993           0 :             PQgetvalue(poResult, i, nField),
    1994           0 :             (GetPrimaryKeyRef() != nullptr) ? PQgetvalue(poResult, i, 0)
    1995             :                                             : nullptr,
    1996             :             nBandsFetched, poBandMetaData);
    1997           0 :         if (poRTDS == nullptr)
    1998           0 :             continue;
    1999             : 
    2000           0 :         if (nOverviewFactor == 1 && resolutionStrategy != USER_RESOLUTION)
    2001             :         {
    2002           0 :             double tilePixelSizeX = poRTDS->adfGeoTransform[GEOTRSFRM_WE_RES];
    2003           0 :             double tilePixelSizeY = poRTDS->adfGeoTransform[GEOTRSFRM_NS_RES];
    2004             : 
    2005           0 :             if (nValidTiles == 0)
    2006             :             {
    2007           0 :                 adfGeoTransform[GEOTRSFRM_WE_RES] = tilePixelSizeX;
    2008           0 :                 adfGeoTransform[GEOTRSFRM_NS_RES] = tilePixelSizeY;
    2009             :             }
    2010             :             else
    2011             :             {
    2012           0 :                 UpdateGlobalResolutionWithTileResolution(tilePixelSizeX,
    2013             :                                                          tilePixelSizeY);
    2014             :             }
    2015             :         }
    2016             : 
    2017           0 :         papoSourcesHolders[nValidTiles++] = poRTDS;
    2018             :     }  // end for
    2019             : 
    2020           0 :     l_nTiles = nValidTiles;
    2021             : 
    2022           0 :     if (nOverviewFactor > 1)
    2023             :     {
    2024           0 :         adfGeoTransform[GEOTRSFRM_WE_RES] =
    2025           0 :             poParentDS->adfGeoTransform[GEOTRSFRM_WE_RES] * nOverviewFactor;
    2026           0 :         adfGeoTransform[GEOTRSFRM_NS_RES] =
    2027           0 :             poParentDS->adfGeoTransform[GEOTRSFRM_NS_RES] * nOverviewFactor;
    2028             :     }
    2029           0 :     else if ((resolutionStrategy == AVERAGE_RESOLUTION ||
    2030           0 :               resolutionStrategy == AVERAGE_APPROX_RESOLUTION) &&
    2031             :              l_nTiles > 0)
    2032             :     {
    2033           0 :         adfGeoTransform[GEOTRSFRM_WE_RES] /= l_nTiles;
    2034           0 :         adfGeoTransform[GEOTRSFRM_NS_RES] /= l_nTiles;
    2035             :     }
    2036             : 
    2037             :     /**
    2038             :      * Complete the rest of geotransform parameters
    2039             :      **/
    2040           0 :     if (adfGeoTransform[GEOTRSFRM_NS_RES] >= 0.0)
    2041           0 :         adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] = ymin;
    2042             :     else
    2043           0 :         adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] = ymax;
    2044             : 
    2045             : #ifdef DEBUG_VERBOSE
    2046             :     CPLDebug("PostGIS_Raster",
    2047             :              "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    2048             :              "GeoTransform array = (%f, %f, %f, %f, %f, %f)",
    2049             :              adfGeoTransform[GEOTRSFRM_TOPLEFT_X],
    2050             :              adfGeoTransform[GEOTRSFRM_WE_RES],
    2051             :              adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1],
    2052             :              adfGeoTransform[GEOTRSFRM_TOPLEFT_Y],
    2053             :              adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2],
    2054             :              adfGeoTransform[GEOTRSFRM_NS_RES]);
    2055             : #endif
    2056             : 
    2057             :     // Calculate the raster size from the geotransform array
    2058           0 :     nRasterXSize = static_cast<int>(
    2059           0 :         fabs(rint((xmax - xmin) / adfGeoTransform[GEOTRSFRM_WE_RES])));
    2060             : 
    2061           0 :     nRasterYSize = static_cast<int>(
    2062           0 :         fabs(rint((ymax - ymin) / adfGeoTransform[GEOTRSFRM_NS_RES])));
    2063             : 
    2064             : #ifdef DEBUG_VERBOSE
    2065             :     CPLDebug("PostGIS_Raster",
    2066             :              "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    2067             :              "Raster size: (%d, %d), ",
    2068             :              nRasterXSize, nRasterYSize);
    2069             : #endif
    2070             : 
    2071           0 :     if (nRasterXSize <= 0 || nRasterYSize <= 0)
    2072             :     {
    2073           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    2074             :                     "Computed PostGIS Raster dimension is invalid. You've "
    2075             :                     "probably specified inappropriate resolution.");
    2076             : 
    2077           0 :         VSIFree(poBandMetaData);
    2078           0 :         return false;
    2079             :     }
    2080             : 
    2081             :     /*******************************************************************
    2082             :      * Now construct the dataset bands
    2083             :      ******************************************************************/
    2084           0 :     BuildBands(poBandMetaData, nBandsFetched);
    2085             : 
    2086             :     // And free bandmetadata
    2087           0 :     VSIFree(poBandMetaData);
    2088             : 
    2089             :     /*******************************************************************
    2090             :      * Finally, add complex sources and create a quadtree index for them
    2091             :      ******************************************************************/
    2092             : #ifdef DEBUG_VERBOSE
    2093             :     CPLDebug("PostGIS_Raster",
    2094             :              "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    2095             :              "Finally, adding sources for bands");
    2096             : #endif
    2097           0 :     for (int iSource = 0; iSource < l_nTiles; iSource++)
    2098             :     {
    2099           0 :         PostGISRasterTileDataset *poRTDS = papoSourcesHolders[iSource];
    2100           0 :         AddComplexSource(poRTDS);
    2101           0 :         if (poRTDS->pszPKID != nullptr)
    2102           0 :             oMapPKIDToRTDS[poRTDS->pszPKID] = poRTDS;
    2103           0 :         CPLQuadTreeInsert(hQuadTree, poRTDS);
    2104             :     }
    2105             : 
    2106           0 :     return true;
    2107             : }
    2108             : 
    2109             : /***********************************************************************
    2110             :  * \brief Construct subdatasets and show them.
    2111             :  *
    2112             :  * This method is not very elegant. It is strongly attached to
    2113             :  * SetRasterProperties (it assumes poResult is not NULL, and the actual
    2114             :  * results are stored at fixed positions). I just did it to avoid a
    2115             :  * huge SetRasterProperties method.
    2116             :  *
    2117             :  * I know, this could be avoided in a better way. Like implementing a
    2118             :  * wrapper to raise queries and get results without all the checking
    2119             :  * overhead. I'd like to do it, someday...
    2120             :  **********************************************************************/
    2121           0 : GBool PostGISRasterDataset::YieldSubdatasets(
    2122             :     PGresult *poResult, const char *pszValidConnectionString)
    2123             : {
    2124           0 :     int l_nTiles = PQntuples(poResult);
    2125           0 :     int i = 0;
    2126             : 
    2127           0 :     papszSubdatasets =
    2128           0 :         static_cast<char **>(VSICalloc(2 * l_nTiles + 1, sizeof(char *)));
    2129           0 :     if (papszSubdatasets == nullptr)
    2130           0 :         return false;
    2131             : 
    2132           0 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    2133             : 
    2134             :     // Subdatasets identified by primary key
    2135           0 :     if (GetPrimaryKeyRef() != nullptr)
    2136             :     {
    2137           0 :         CPLString osPrimaryKeyNameI(CPLQuotedSQLIdentifier(pszPrimaryKeyName));
    2138             : 
    2139           0 :         for (i = 0; i < l_nTiles; i++)
    2140             :         {
    2141             : 
    2142           0 :             const char *pszId = PQgetvalue(poResult, i, 0);
    2143             : 
    2144           0 :             papszSubdatasets[2 * i] = CPLStrdup(CPLSPrintf(
    2145             :                 "SUBDATASET_%d_NAME=PG:%s schema='%s' table='%s' column='%s' "
    2146             :                 "where='%s = %s'",
    2147             :                 i + 1, pszValidConnectionString, pszSchema, pszTable, pszColumn,
    2148             :                 osPrimaryKeyNameI.c_str(), pszId));
    2149             : 
    2150           0 :             papszSubdatasets[2 * i + 1] = CPLStrdup(CPLSPrintf(
    2151             :                 "SUBDATASET_%d_DESC=PostGIS Raster at %s.%s (%s), with %s = %s",
    2152             :                 i + 1, pszSchema, pszTable, pszColumn,
    2153             :                 osPrimaryKeyNameI.c_str(), pszId));
    2154             :         }
    2155             :     }
    2156             : 
    2157             :     // Subdatasets identified by upper left pixel
    2158             :     else
    2159             :     {
    2160           0 :         for (i = 0; i < l_nTiles; i++)
    2161             :         {
    2162           0 :             char *pszRes = CPLStrdup(PQgetvalue(poResult, i, 0));
    2163             : 
    2164             :             // Skip first "("
    2165           0 :             char *pszFilteredRes = pszRes + 1;
    2166             : 
    2167             :             // Skip last ")"
    2168           0 :             pszFilteredRes[strlen(pszFilteredRes) - 1] = '\0';
    2169             : 
    2170             :             // Tokenize
    2171             :             char **papszParams =
    2172           0 :                 CSLTokenizeString2(pszFilteredRes, ",", CSLT_HONOURSTRINGS);
    2173             : 
    2174           0 :             CPLFree(pszRes);
    2175             : 
    2176             :             const double dfTileUpperLeftX =
    2177           0 :                 CPLAtof(papszParams[POS_UPPERLEFTX]);
    2178             :             const double dfTileUpperLeftY =
    2179           0 :                 CPLAtof(papszParams[POS_UPPERLEFTY]);
    2180             : 
    2181           0 :             papszSubdatasets[2 * i] = CPLStrdup(CPLSPrintf(
    2182             :                 "SUBDATASET_%d_NAME=PG:%s schema=%s table=%s column=%s "
    2183             :                 "where='abs(ST_UpperLeftX(%s) - %.8f) < 1e-8 AND "
    2184             :                 "abs(ST_UpperLeftY(%s) - %.8f) < 1e-8'",
    2185             :                 i + 1, pszValidConnectionString, pszSchema, pszTable, pszColumn,
    2186             :                 osColumnI.c_str(), dfTileUpperLeftX, osColumnI.c_str(),
    2187             :                 dfTileUpperLeftY));
    2188             : 
    2189           0 :             papszSubdatasets[2 * i + 1] = CPLStrdup(
    2190             :                 CPLSPrintf("SUBDATASET_%d_DESC=PostGIS Raster at %s.%s (%s), "
    2191             :                            "UpperLeft = %.8f, %.8f",
    2192             :                            i + 1, pszSchema, pszTable, pszColumn,
    2193             :                            dfTileUpperLeftX, dfTileUpperLeftY));
    2194             : 
    2195           0 :             CSLDestroy(papszParams);
    2196             :         }
    2197             :     }
    2198             : 
    2199             :     /**
    2200             :      * Not a single raster fetched. Not really needed. Just to keep code clean
    2201             :      **/
    2202           0 :     nRasterXSize = 0;
    2203           0 :     nRasterYSize = 0;
    2204           0 :     adfGeoTransform[GEOTRSFRM_TOPLEFT_X] = 0.0;
    2205           0 :     adfGeoTransform[GEOTRSFRM_WE_RES] = 1.0;
    2206           0 :     adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1] = 0.0;
    2207           0 :     adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] = 0.0;
    2208           0 :     adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] = 0.0;
    2209           0 :     adfGeoTransform[GEOTRSFRM_NS_RES] = -1.0;
    2210             : 
    2211           0 :     return true;
    2212             : }
    2213             : 
    2214             : /***********************************************************************
    2215             :  * \brief Set the general raster properties.
    2216             :  *
    2217             :  * This method is called when the driver working mode is
    2218             :  * ONE_RASTER_PER_ROW or ONE_RASTER_PER_TABLE.
    2219             :  *
    2220             :  * We must distinguish between tiled and untiled raster coverages. In
    2221             :  * PostGIS Raster, there's no real difference between 'tile' and
    2222             :  * 'raster'. There's only 'raster objects'. Each record of a raster
    2223             :  * table is a raster object, and has its own georeference information,
    2224             :  * whether if the record is a tile of a bigger raster coverage or is a
    2225             :  * complete raster. So, <b>there's no a way of knowing if the rows of a
    2226             :  * raster table are related or not</b>. It is user's responsibility, and
    2227             :  * it is managed by 'mode' parameter in connection string, which
    2228             :  * determines the driver working mode.
    2229             :  *
    2230             :  * The user is responsible to ensure that the raster layer meets the
    2231             :  * minimum topological requirements for analysis. The ideal case is when
    2232             :  * all the raster tiles of a continuous layer are the same size, snap to
    2233             :  * the same grid and do not overlap.
    2234             :  *
    2235             :  **********************************************************************/
    2236           2 : GBool PostGISRasterDataset::SetRasterProperties(
    2237             :     const char *pszValidConnectionString)
    2238             : {
    2239           2 :     PGresult *poResult = nullptr;
    2240           2 :     GBool bDataFoundInRasterColumns = false;
    2241           2 :     GBool bNeedToCheckWholeTable = false;
    2242             : 
    2243           4 :     CPLString osCommand;
    2244           4 :     CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    2245           4 :     CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
    2246           4 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    2247             : 
    2248             :     /*******************************************************************
    2249             :      * Get the extent and the maximum number of bands of the requested
    2250             :      * raster-
    2251             :      *
    2252             :      * TODO: The extent of rotated rasters could be a problem. We will
    2253             :      * need a ST_RotatedExtent function in PostGIS. Without that
    2254             :      * function, we should not allow rotated rasters.
    2255             :      ******************************************************************/
    2256           2 :     if (pszWhere != nullptr)
    2257             :     {
    2258             :         osCommand.Printf(
    2259             :             "select srid, nbband, ST_XMin(geom) as xmin, "
    2260             :             "ST_XMax(geom) as xmax, ST_YMin(geom) as ymin, "
    2261             :             "ST_YMax(geom) as ymax, scale_x, scale_y from (select ST_SRID(%s) "
    2262             :             "srid, "
    2263             :             "ST_Extent(%s::geometry) geom, max(ST_NumBands(%s)) "
    2264             :             "nbband, avg(ST_ScaleX(%s)) scale_x, avg(ST_ScaleY(%s)) scale_y "
    2265             :             "from %s.%s where %s group by ST_SRID(%s)) foo",
    2266             :             osColumnI.c_str(), osColumnI.c_str(), osColumnI.c_str(),
    2267             :             osColumnI.c_str(), osColumnI.c_str(), osSchemaI.c_str(),
    2268           0 :             osTableI.c_str(), pszWhere, osColumnI.c_str());
    2269             : 
    2270             : #ifdef DEBUG_QUERY
    2271             :         CPLDebug("PostGIS_Raster",
    2272             :                  "PostGISRasterDataset::SetRasterProperties(): First query: %s",
    2273             :                  osCommand.c_str());
    2274             : #endif
    2275             : 
    2276           0 :         poResult = PQexec(poConn, osCommand.c_str());
    2277             :     }
    2278             :     else
    2279             :     {
    2280             :         /**
    2281             :          * Optimization: First, check raster_columns view (it makes
    2282             :          * things faster. See ticket #5046)
    2283             :          *
    2284             :          * This can only be applied if we don't have a 'where' clause,
    2285             :          * because raster_columns view stores statistics about the whole
    2286             :          * table. If the user specified 'where' clause is because is
    2287             :          * just interested in a subset of the table rows.
    2288             :          **/
    2289             :         osCommand.Printf(
    2290             :             "select srid, nbband, ST_XMin(geom) as xmin, ST_XMax(geom) "
    2291             :             "as xmax, ST_YMin(geom) as ymin, ST_YMax(geom) as ymax, "
    2292             :             "scale_x, scale_y, blocksize_x, blocksize_y, same_alignment, "
    2293             :             "regular_blocking "
    2294             :             "from (select srid, extent geom, num_bands nbband, "
    2295             :             "scale_x, scale_y, blocksize_x, blocksize_y, same_alignment, "
    2296             :             "regular_blocking from "
    2297             :             "raster_columns where r_table_schema = '%s' and "
    2298             :             "r_table_name = '%s' and r_raster_column = '%s' ) foo",
    2299           2 :             pszSchema, pszTable, pszColumn);
    2300             : 
    2301             : #ifdef DEBUG_QUERY
    2302             :         CPLDebug("PostGIS_Raster",
    2303             :                  "PostGISRasterDataset::SetRasterProperties(): First query: %s",
    2304             :                  osCommand.c_str());
    2305             : #endif
    2306             : 
    2307           2 :         poResult = PQexec(poConn, osCommand.c_str());
    2308             : 
    2309             :         // Query execution error
    2310           4 :         if (poResult == nullptr ||
    2311           4 :             PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    2312           2 :             PQntuples(poResult) < 0)
    2313             :         {
    2314           0 :             bNeedToCheckWholeTable = true;
    2315             : 
    2316           0 :             if (poResult)
    2317           0 :                 PQclear(poResult);
    2318             :         }
    2319             : 
    2320             :         /**
    2321             :          * We didn't find anything in raster_columns view. Need to check
    2322             :          * the whole table for metadata
    2323             :          **/
    2324           2 :         else if (PQntuples(poResult) == 0)
    2325             :         {
    2326             : 
    2327           2 :             ReportError(
    2328             :                 CE_Warning, CPLE_AppDefined,
    2329             :                 "Cannot find "
    2330             :                 "information about %s.%s table in raster_columns view. The "
    2331             :                 "raster table load would take a lot of time. Please, "
    2332             :                 "execute AddRasterConstraints PostGIS function to register "
    2333             :                 "this table as raster table in raster_columns view. This "
    2334             :                 "will save a lot of time.",
    2335             :                 pszSchema, pszTable);
    2336             : 
    2337           2 :             PQclear(poResult);
    2338             : 
    2339           2 :             bNeedToCheckWholeTable = true;
    2340             :         }
    2341             : 
    2342             :         /* There's a result but the row has empty values */
    2343           0 :         else if (PQntuples(poResult) == 1 &&
    2344           0 :                  (PQgetvalue(poResult, 0, 1)[0] == '\0' ||
    2345           0 :                   (poParentDS == nullptr &&
    2346           0 :                    (PQgetvalue(poResult, 0, 2)[0] == '\0' ||
    2347           0 :                     PQgetvalue(poResult, 0, 3)[0] == '\0' ||
    2348           0 :                     PQgetvalue(poResult, 0, 4)[0] == '\0' ||
    2349           0 :                     PQgetvalue(poResult, 0, 5)[0] == '\0'))))
    2350             :         {
    2351           0 :             ReportError(
    2352             :                 CE_Warning, CPLE_AppDefined,
    2353             :                 "Cannot find (valid) "
    2354             :                 "information about %s.%s table in raster_columns view. The "
    2355             :                 "raster table load would take a lot of time. Please, "
    2356             :                 "execute AddRasterConstraints PostGIS function to register "
    2357             :                 "this table as raster table in raster_columns view. This "
    2358             :                 "will save a lot of time.",
    2359             :                 pszSchema, pszTable);
    2360             : 
    2361           0 :             PQclear(poResult);
    2362             : 
    2363           0 :             bNeedToCheckWholeTable = true;
    2364             :         }
    2365             : 
    2366             :         // We should check whole table but we can't
    2367           2 :         if (bNeedToCheckWholeTable && !bCheckAllTiles)
    2368             :         {
    2369           0 :             ReportError(
    2370             :                 CE_Failure, CPLE_AppDefined,
    2371             :                 "Cannot find "
    2372             :                 "information about %s.%s table in raster_columns view. "
    2373             :                 "Please, execute AddRasterConstraints PostGIS function to "
    2374             :                 "register this table as raster table in raster_columns "
    2375             :                 "view. This will save a lot of time. As alternative, "
    2376             :                 "provide configuration option "
    2377             :                 "PR_ALLOW_WHOLE_TABLE_SCAN=YES. With this option, the "
    2378             :                 "driver will work even without the table information "
    2379             :                 "stored in raster_columns view, but it could perform "
    2380             :                 "really slow.",
    2381             :                 pszSchema, pszTable);
    2382             : 
    2383           0 :             PQclear(poResult);
    2384             : 
    2385           0 :             return false;
    2386             :         }
    2387             : 
    2388             :         // We should check the whole table and we can
    2389           2 :         else if (bNeedToCheckWholeTable)
    2390             :         {
    2391             :             osCommand.Printf(
    2392             :                 "select srid, nbband, st_xmin(geom) as xmin, "
    2393             :                 "st_xmax(geom) as xmax, st_ymin(geom) as ymin, "
    2394             :                 "st_ymax(geom) as ymax, scale_x, scale_y from (select "
    2395             :                 "st_srid(%s) srid, "
    2396             :                 "st_extent(%s::geometry) geom, max(ST_NumBands(%s)) "
    2397             :                 "nbband, avg(ST_ScaleX(%s)) scale_x, avg(ST_ScaleY(%s)) "
    2398             :                 "scale_y from %s.%s group by st_srid(%s)) foo",
    2399             :                 osColumnI.c_str(), osColumnI.c_str(), osColumnI.c_str(),
    2400             :                 osColumnI.c_str(), osColumnI.c_str(), osSchemaI.c_str(),
    2401           2 :                 osTableI.c_str(), osColumnI.c_str());
    2402             : 
    2403             : #ifdef DEBUG_QUERY
    2404             :             CPLDebug("PostGIS_Raster",
    2405             :                      "PostGISRasterDataset::SetRasterProperties(): "
    2406             :                      "First query: %s",
    2407             :                      osCommand.c_str());
    2408             : #endif
    2409             : 
    2410           2 :             poResult = PQexec(poConn, osCommand.c_str());
    2411             :         }
    2412             : 
    2413             :         // We already found the data in raster_columns
    2414             :         else
    2415             :         {
    2416           0 :             bDataFoundInRasterColumns = true;
    2417             :         }
    2418             :     }
    2419             : 
    2420             :     // Query execution error
    2421           2 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    2422           0 :         PQntuples(poResult) < 0)
    2423             :     {
    2424             : 
    2425           2 :         ReportError(CE_Failure, CPLE_AppDefined,
    2426             :                     "Error browsing database for PostGIS Raster "
    2427             :                     "properties : %s",
    2428           2 :                     PQerrorMessage(poConn));
    2429             : 
    2430           2 :         if (poResult != nullptr)
    2431           2 :             PQclear(poResult);
    2432             : 
    2433           2 :         return false;
    2434             :     }
    2435             : 
    2436           0 :     else if (PQntuples(poResult) == 0)
    2437             :     {
    2438           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    2439             :                     "No results found in %s.%s. Did you specify a 'where' "
    2440             :                     "clause too restrictive?",
    2441             :                     pszSchema, pszTable);
    2442             : 
    2443           0 :         PQclear(poResult);
    2444             : 
    2445           0 :         return false;
    2446             :     }
    2447             : 
    2448             :     /**
    2449             :      * Found more than one SRID value in the table. Not allowed.
    2450             :      *
    2451             :      * TODO: We could provide an extra parameter, to transform all the
    2452             :      * tiles to the same SRID
    2453             :      **/
    2454           0 :     else if (PQntuples(poResult) > 1)
    2455             :     {
    2456           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    2457             :                     "Error, the table %s.%s contains tiles with different "
    2458             :                     "srid. This feature is not yet supported by the PostGIS "
    2459             :                     "Raster driver. Please, specify a table that contains only "
    2460             :                     "tiles with the same srid or provide a 'where' constraint "
    2461             :                     "to select just the tiles with the same value for srid",
    2462             :                     pszSchema, pszTable);
    2463             : 
    2464           0 :         PQclear(poResult);
    2465             : 
    2466           0 :         return false;
    2467             :     }
    2468             : 
    2469             :     // Get some information we will probably need further
    2470           0 :     nSrid = atoi(PQgetvalue(poResult, 0, 0));
    2471           0 :     nBandsToCreate = atoi(PQgetvalue(poResult, 0, 1));
    2472           0 :     if (poParentDS != nullptr)
    2473             :     {
    2474             :         /* If we are an overview of a parent dataset, we need to adjust */
    2475             :         /* to its extent, otherwise IRasterIO() will not work properly */
    2476           0 :         xmin = poParentDS->xmin;
    2477           0 :         xmax = poParentDS->xmax;
    2478           0 :         ymin = poParentDS->ymin;
    2479           0 :         ymax = poParentDS->ymax;
    2480             :     }
    2481             :     else
    2482             :     {
    2483           0 :         xmin = CPLAtof(PQgetvalue(poResult, 0, 2));
    2484           0 :         xmax = CPLAtof(PQgetvalue(poResult, 0, 3));
    2485           0 :         ymin = CPLAtof(PQgetvalue(poResult, 0, 4));
    2486           0 :         ymax = CPLAtof(PQgetvalue(poResult, 0, 5));
    2487             :     }
    2488             : 
    2489             :     // Create the QuadTree object
    2490             :     CPLRectObj sRect;
    2491           0 :     sRect.minx = xmin;
    2492           0 :     sRect.miny = ymin;
    2493           0 :     sRect.maxx = xmax;
    2494           0 :     sRect.maxy = ymax;
    2495           0 :     hQuadTree = CPLQuadTreeCreate(&sRect, GetTileBoundingBox);
    2496             : 
    2497           0 :     double scale_x = CPLAtof(PQgetvalue(poResult, 0, 6));
    2498           0 :     double scale_y = CPLAtof(PQgetvalue(poResult, 0, 7));
    2499           0 :     if (nOverviewFactor > 1 && poParentDS != nullptr)
    2500             :     {
    2501           0 :         scale_x =
    2502           0 :             poParentDS->adfGeoTransform[GEOTRSFRM_WE_RES] * nOverviewFactor;
    2503           0 :         scale_y =
    2504           0 :             poParentDS->adfGeoTransform[GEOTRSFRM_NS_RES] * nOverviewFactor;
    2505             :     }
    2506           0 :     else if (resolutionStrategy == USER_RESOLUTION)
    2507             :     {
    2508           0 :         scale_x = adfGeoTransform[GEOTRSFRM_WE_RES];
    2509           0 :         scale_y = adfGeoTransform[GEOTRSFRM_NS_RES];
    2510             :     }
    2511             : 
    2512             :     // These fields can only be fetched from raster_columns view
    2513           0 :     if (bDataFoundInRasterColumns)
    2514             :     {
    2515           0 :         nTileWidth = atoi(PQgetvalue(poResult, 0, 8));
    2516           0 :         nTileHeight = atoi(PQgetvalue(poResult, 0, 9));
    2517           0 :         if (nTileWidth != 0 && nTileHeight != 0)
    2518           0 :             bTilesSameDimension = true;
    2519             : 
    2520           0 :         bAllTilesSnapToSameGrid = EQUAL(PQgetvalue(poResult, 0, 10), "t");
    2521             : 
    2522           0 :         bRegularBlocking = EQUAL(PQgetvalue(poResult, 0, 11), "t");
    2523             :     }
    2524             : 
    2525             : #ifdef DEBUG_VERBOSE
    2526             :     CPLDebug("PostGIS_Raster",
    2527             :              "PostGISRasterDataset::SetRasterProperties: xmin = %f, "
    2528             :              "xmax = %f, ymin = %f, ymax = %f, scale_x = %f, scale_y = %f",
    2529             :              xmin, xmax, ymin, ymax, scale_x, scale_y);
    2530             : #endif
    2531             : 
    2532           0 :     PQclear(poResult);
    2533             : 
    2534             :     /*******************************************************************
    2535             :      * Now, we fetch the metadata of all the raster tiles in the
    2536             :      * database, that will allow us to construct VRT sources to the
    2537             :      * PostGIS Raster bands.
    2538             :      *
    2539             :      * TODO: Improve this. If we have a big amount of tiles, it can be
    2540             :      * a problem.
    2541             :      ******************************************************************/
    2542             :     // We'll identify each tile for its primary key/unique id (ideal)
    2543           0 :     if (GetPrimaryKeyRef() != nullptr)
    2544             :     {
    2545           0 :         CPLString osPrimaryKeyNameI(CPLQuotedSQLIdentifier(pszPrimaryKeyName));
    2546             : 
    2547           0 :         if (pszWhere == nullptr)
    2548             :         {
    2549             :             /* If we don't know the pixel size, then guess it from averaging the
    2550             :              * metadata */
    2551             :             /* of a maximum 10 rasters */
    2552           0 :             if (bIsFastPK && nMode == ONE_RASTER_PER_TABLE &&
    2553           0 :                 HasSpatialIndex() && (scale_x == 0 || scale_y == 0) &&
    2554           0 :                 resolutionStrategy == AVERAGE_APPROX_RESOLUTION)
    2555             :             {
    2556             :                 osCommand.Printf("SELECT avg(scale_x) avg_scale_x, "
    2557             :                                  "avg(scale_y) avg_scale_y FROM "
    2558             :                                  "(SELECT ST_ScaleX(%s) scale_x, ST_ScaleY(%s) "
    2559             :                                  "scale_y FROM %s.%s LIMIT 10) foo",
    2560             :                                  osColumnI.c_str(), osColumnI.c_str(),
    2561           0 :                                  osSchemaI.c_str(), osTableI.c_str());
    2562             : #ifdef DEBUG_QUERY
    2563             :                 CPLDebug(
    2564             :                     "PostGIS_Raster",
    2565             :                     "PostGISRasterDataset::SetRasterProperties(): Query: %s",
    2566             :                     osCommand.c_str());
    2567             : #endif
    2568             : 
    2569           0 :                 poResult = PQexec(poConn, osCommand.c_str());
    2570           0 :                 if (poResult == nullptr ||
    2571           0 :                     PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    2572           0 :                     PQntuples(poResult) <= 0)
    2573             :                 {
    2574             : 
    2575           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
    2576             :                                 "Error retrieving raster metadata");
    2577             : 
    2578           0 :                     CPLDebug("PostGIS_Raster",
    2579             :                              "PostGISRasterDataset::SetRasterProperties(): %s",
    2580           0 :                              PQerrorMessage(poConn));
    2581             : 
    2582           0 :                     if (poResult != nullptr)
    2583           0 :                         PQclear(poResult);
    2584             : 
    2585           0 :                     return false;
    2586             :                 }
    2587             : 
    2588           0 :                 scale_x = CPLAtof(PQgetvalue(poResult, 0, 0));
    2589           0 :                 scale_y = CPLAtof(PQgetvalue(poResult, 0, 1));
    2590           0 :                 CPLDebug("PostGIS_Raster",
    2591             :                          "PostGISRasterDataset::SetRasterProperties: guessed: "
    2592             :                          "scale_x = %f, scale_y = %f",
    2593             :                          scale_x, scale_y);
    2594             : 
    2595           0 :                 PQclear(poResult);
    2596             :             }
    2597             : 
    2598             :             /* If we build a raster for the whole table, than we have a spatial
    2599             :                index, and a primary key, and we know the pixel size, we can
    2600             :                build the dataset immediately, and IRasterIO() queries will be
    2601             :                able to retrieve quickly the PKID of the tiles to fetch, so we
    2602             :                don't need to scan the whole table */
    2603           0 :             if (bIsFastPK && nMode == ONE_RASTER_PER_TABLE &&
    2604           0 :                 HasSpatialIndex() && scale_x != 0 && scale_y != 0)
    2605             :             {
    2606           0 :                 adfGeoTransform[GEOTRSFRM_TOPLEFT_X] = xmin;
    2607           0 :                 adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1] = 0.0;
    2608           0 :                 adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] =
    2609           0 :                     (scale_y < 0) ? ymax : ymin;
    2610           0 :                 adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] = 0.0;
    2611           0 :                 adfGeoTransform[GEOTRSFRM_WE_RES] = scale_x;
    2612           0 :                 adfGeoTransform[GEOTRSFRM_NS_RES] = scale_y;
    2613             : 
    2614             :                 // Calculate the raster size from the geotransform array
    2615           0 :                 nRasterXSize = static_cast<int>(fabs(
    2616           0 :                     rint((xmax - xmin) / adfGeoTransform[GEOTRSFRM_WE_RES])));
    2617             : 
    2618           0 :                 nRasterYSize = static_cast<int>(fabs(
    2619           0 :                     rint((ymax - ymin) / adfGeoTransform[GEOTRSFRM_NS_RES])));
    2620             : 
    2621             : #ifdef DEBUG_VERBOSE
    2622             :                 CPLDebug("PostGIS_Raster",
    2623             :                          "PostGISRasterDataset::ConstructOneDatasetFromTiles: "
    2624             :                          "Raster size: (%d, %d), ",
    2625             :                          nRasterXSize, nRasterYSize);
    2626             : #endif
    2627             : 
    2628           0 :                 if (nRasterXSize <= 0 || nRasterYSize <= 0)
    2629             :                 {
    2630           0 :                     ReportError(
    2631             :                         CE_Failure, CPLE_AppDefined,
    2632             :                         "Computed PostGIS Raster dimension is invalid. You "
    2633             :                         "have probably specified an inappropriate "
    2634             :                         "resolution.");
    2635             : 
    2636           0 :                     return false;
    2637             :                 }
    2638             : 
    2639           0 :                 bBuildQuadTreeDynamically = true;
    2640             : 
    2641             :                 /**************************************************************
    2642             :                  * Now construct the dataset bands
    2643             :                  ***************************************************************/
    2644           0 :                 int nBandsFetched = 0;
    2645           0 :                 BandMetadata *poBandMetaData = GetBandsMetadata(&nBandsFetched);
    2646             : 
    2647           0 :                 BuildBands(poBandMetaData, nBandsFetched);
    2648             : 
    2649             :                 // And free bandmetadata
    2650           0 :                 VSIFree(poBandMetaData);
    2651             : 
    2652           0 :                 return true;
    2653             :             }
    2654             : 
    2655             :             osCommand.Printf("select %s, st_metadata(%s) from %s.%s",
    2656             :                              osPrimaryKeyNameI.c_str(), osColumnI.c_str(),
    2657           0 :                              osSchemaI.c_str(), osTableI.c_str());
    2658             : 
    2659             :             // srid should not be necessary. It was previously checked
    2660             :         }
    2661             : 
    2662             :         else
    2663             :         {
    2664             :             osCommand.Printf("select %s, st_metadata(%s) from %s.%s "
    2665             :                              "where %s",
    2666             :                              osPrimaryKeyNameI.c_str(), osColumnI.c_str(),
    2667           0 :                              osSchemaI.c_str(), osTableI.c_str(), pszWhere);
    2668             :         }
    2669             :     }
    2670             : 
    2671             :     // No primary key/unique id found. We rely on upper left pixel
    2672             :     else
    2673             :     {
    2674           0 :         if (pszWhere == nullptr)
    2675             :         {
    2676             :             osCommand.Printf("select st_metadata(%s) from %s.%s",
    2677             :                              osColumnI.c_str(), osSchemaI.c_str(),
    2678           0 :                              osTableI.c_str());
    2679             :         }
    2680             : 
    2681             :         else
    2682             :         {
    2683             :             osCommand.Printf("select st_metadata(%s) from %s.%s where %s",
    2684             :                              osColumnI.c_str(), osSchemaI.c_str(),
    2685           0 :                              osTableI.c_str(), pszWhere);
    2686             :         }
    2687             :     }
    2688             : 
    2689             : #ifdef DEBUG_QUERY
    2690             :     CPLDebug("PostGIS_Raster",
    2691             :              "PostGISRasterDataset::SetRasterProperties(): Query: %s",
    2692             :              osCommand.c_str());
    2693             : #endif
    2694             : 
    2695           0 :     poResult = PQexec(poConn, osCommand.c_str());
    2696           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_TUPLES_OK ||
    2697           0 :         PQntuples(poResult) <= 0)
    2698             :     {
    2699             : 
    2700           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    2701             :                     "Error retrieving raster metadata");
    2702             : 
    2703           0 :         CPLDebug("PostGIS_Raster",
    2704             :                  "PostGISRasterDataset::SetRasterProperties(): %s",
    2705           0 :                  PQerrorMessage(poConn));
    2706             : 
    2707           0 :         if (poResult != nullptr)
    2708           0 :             PQclear(poResult);
    2709             : 
    2710           0 :         return false;
    2711             :     }
    2712             : 
    2713             :     // Now we know the number of tiles that form our dataset
    2714           0 :     m_nTiles = PQntuples(poResult);
    2715             : 
    2716             :     /*******************************************************************
    2717             :      * We are going to create a whole dataset as a mosaic with all the
    2718             :      * tiles. We'll consider each tile as a VRT source for
    2719             :      * PostGISRasterRasterBand. The data will be actually read by each
    2720             :      * of these sources, and it will be cached in the sources' caches,
    2721             :      * not in the PostGISRasterRasterBand cache
    2722             :      ******************************************************************/
    2723           0 :     if (m_nTiles == 1 || nMode == ONE_RASTER_PER_TABLE)
    2724             :     {
    2725             : #ifdef DEBUG_VERBOSE
    2726             :         CPLDebug("PostGIS_Raster",
    2727             :                  "PostGISRasterDataset::SetRasterProperties(): "
    2728             :                  "Constructing one dataset from %d tiles",
    2729             :                  m_nTiles);
    2730             : #endif
    2731             : 
    2732           0 :         GBool res = ConstructOneDatasetFromTiles(poResult);
    2733             : 
    2734           0 :         PQclear(poResult);
    2735             : 
    2736           0 :         return res;
    2737             :     }
    2738             : 
    2739             :     /***************************************************************
    2740             :      * One raster per row: collect subdatasets
    2741             :      **************************************************************/
    2742           0 :     else if (nMode == ONE_RASTER_PER_ROW)
    2743             :     {
    2744             : #ifdef DEBUG_VERBOSE
    2745             :         CPLDebug("PostGIS_Raster",
    2746             :                  "PostGISRasterDataset::SetRasterProperties(): "
    2747             :                  "Reporting %d datasets",
    2748             :                  m_nTiles);
    2749             : #endif
    2750             : 
    2751           0 :         GBool res = YieldSubdatasets(poResult, pszValidConnectionString);
    2752             : 
    2753           0 :         PQclear(poResult);
    2754             : 
    2755           0 :         return res;
    2756             :     }
    2757             : 
    2758             :     /***************************************************************
    2759             :      * Wrong mode: error
    2760             :      **************************************************************/
    2761             :     else
    2762             :     {
    2763           0 :         ReportError(CE_Failure, CPLE_AppDefined,
    2764             :                     "Wrong driver working mode. You must specify mode = 1 or "
    2765             :                     "mode = 2 in the connection string. Check PostGIS Raster "
    2766             :                     "documentation at "
    2767             :                     "http://trac.osgeo.org/gdal/wiki/frmts_wtkraster.html "
    2768             :                     "for further information about working modes.");
    2769             : 
    2770           0 :         PQclear(poResult);
    2771             : 
    2772           0 :         return false;
    2773             :     }
    2774             : }
    2775             : 
    2776             : /***********************************************************************
    2777             :  * \brief Get the connection information for a filename.
    2778             :  *
    2779             :  * This method extracts these dataset parameters from the connection
    2780             :  * string, if present:
    2781             :  * - pszSchema: The schema where the table belongs
    2782             :  * - pszTable: The table's name
    2783             :  * - pszColumn: The raster column's name
    2784             :  * - pszWhere: A where constraint to apply to the table's rows
    2785             :  * - pszHost: The PostgreSQL host
    2786             :  * - pszPort: The PostgreSQL port
    2787             :  * - pszUser: The PostgreSQL user
    2788             :  * - pszPassword: The PostgreSQL password
    2789             :  * - nMode: The connection mode
    2790             :  *
    2791             :  * If any of there parameters is not present in the connection string,
    2792             :  * default values are taken. nMode is deducted from the rest of
    2793             :  * parameters if not provided.
    2794             :  *
    2795             :  * Apart from that, bBrowseDatabase is set to TRUE if the mode is
    2796             :  * BROWSE_SCHEMA or BROWSE_DATABASE
    2797             :  **********************************************************************/
    2798             : static GBool
    2799           2 : GetConnectionInfo(const char *pszFilename, char **ppszConnectionString,
    2800             :                   char **ppszService, char **ppszDbname, char **ppszSchema,
    2801             :                   char **ppszTable, char **ppszColumn, char **ppszWhere,
    2802             :                   char **ppszHost, char **ppszPort, char **ppszUser,
    2803             :                   char **ppszPassword, WorkingMode *nMode,
    2804             :                   GBool *bBrowseDatabase, OutDBResolution *peOutDBResolution)
    2805             : {
    2806           2 :     int nPos = -1, sPos = -1, i;
    2807           2 :     char *pszTmp = nullptr;
    2808           2 :     char **papszParams = PostGISRasterParseConnectionString(pszFilename);
    2809           2 :     if (papszParams == nullptr)
    2810             :     {
    2811           0 :         return false;
    2812             :     }
    2813             : 
    2814             :     /*******************************************************************
    2815             :      * Get mode:
    2816             :      *  - 1. ONE_RASTER_PER_ROW: Each row is considered as a separate
    2817             :      *      raster
    2818             :      *  - 2. ONE_RASTER_PER_TABLE: All the table rows are considered as
    2819             :      *      a whole raster coverage
    2820             :      ******************************************************************/
    2821           2 :     nPos = CSLFindName(papszParams, "mode");
    2822           2 :     if (nPos != -1)
    2823             :     {
    2824             :         int tmp;
    2825           0 :         tmp = atoi(CPLParseNameValue(papszParams[nPos], nullptr));
    2826             : 
    2827             :         // default value
    2828           0 :         *nMode = ONE_RASTER_PER_ROW;
    2829             : 
    2830           0 :         if (tmp == 2)
    2831             :         {
    2832           0 :             *nMode = ONE_RASTER_PER_TABLE;
    2833             :         }
    2834             : 
    2835             :         /* Remove the mode from connection string */
    2836           0 :         papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2837             :     }
    2838             :     /* Default mode */
    2839             :     else
    2840           2 :         *nMode = ONE_RASTER_PER_ROW;
    2841             : 
    2842           2 :     nPos = CSLFindName(papszParams, "outdb_resolution");
    2843           2 :     *peOutDBResolution = OutDBResolution::SERVER_SIDE;
    2844           2 :     if (nPos != -1)
    2845             :     {
    2846           0 :         const char *pszValue = CPLParseNameValue(papszParams[nPos], nullptr);
    2847           0 :         if (EQUAL(pszValue, "server_side"))
    2848           0 :             *peOutDBResolution = OutDBResolution::SERVER_SIDE;
    2849           0 :         else if (EQUAL(pszValue, "client_side"))
    2850           0 :             *peOutDBResolution = OutDBResolution::CLIENT_SIDE;
    2851           0 :         else if (EQUAL(pszValue, "client_side_if_possible"))
    2852           0 :             *peOutDBResolution = OutDBResolution::CLIENT_SIDE_IF_POSSIBLE;
    2853             :         else
    2854             :         {
    2855           0 :             CPLError(CE_Failure, CPLE_NotSupported,
    2856             :                      "Unsupported value for outdb_resolution: %s", pszValue);
    2857             :         }
    2858             : 
    2859             :         /* Remove the mode from connection string */
    2860           0 :         papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2861             :     }
    2862             : 
    2863             :     /**
    2864             :      * Case 1: There's no database or service name: Error, you need, at least,
    2865             :      * specify a database or a service name (NOTE: insensitive search)
    2866             :      **/
    2867           2 :     nPos = CSLFindName(papszParams, "dbname");
    2868           2 :     sPos = CSLFindName(papszParams, "service");
    2869             : 
    2870           2 :     if (nPos == -1 && sPos == -1)
    2871             :     {
    2872           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2873             :                  "You must specify at least a db name or a service name");
    2874             : 
    2875           0 :         CSLDestroy(papszParams);
    2876             : 
    2877           0 :         return false;
    2878             :     }
    2879             : 
    2880           2 :     *ppszDbname = (nPos != -1)
    2881           2 :                       ? CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr))
    2882             :                       : nullptr;
    2883           2 :     *ppszService =
    2884           2 :         (sPos != -1) ? CPLStrdup(CPLParseNameValue(papszParams[sPos], nullptr))
    2885             :                      : nullptr;
    2886             : 
    2887             :     /**
    2888             :      * Case 2: There's a database or service name, but no table name: activate a
    2889             :      *flag for browsing the database, fetching all the schemas that contain
    2890             :      * raster tables
    2891             :      **/
    2892           2 :     nPos = CSLFindName(papszParams, "table");
    2893           2 :     if (nPos == -1)
    2894             :     {
    2895           0 :         *bBrowseDatabase = true;
    2896             : 
    2897             :         /* Get schema name, if exist */
    2898           0 :         nPos = CSLFindName(papszParams, "schema");
    2899           0 :         if (nPos != -1)
    2900             :         {
    2901           0 :             *ppszSchema =
    2902           0 :                 CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    2903             : 
    2904             :             /* Delete this pair from params array */
    2905           0 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2906             :         }
    2907             : 
    2908             :         /**
    2909             :          * Remove the rest of the parameters, if exist (they must not be
    2910             :          * present if we want a valid PQ connection string)
    2911             :          **/
    2912           0 :         nPos = CSLFindName(papszParams, "column");
    2913           0 :         if (nPos != -1)
    2914             :         {
    2915             :             /* Delete this pair from params array */
    2916           0 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2917             :         }
    2918             : 
    2919           0 :         nPos = CSLFindName(papszParams, "where");
    2920           0 :         if (nPos != -1)
    2921             :         {
    2922             :             /* Delete this pair from params array */
    2923           0 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2924             :         }
    2925             :     }
    2926             :     else
    2927             :     {
    2928           2 :         *bBrowseDatabase = false;
    2929             : 
    2930           2 :         *ppszTable = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    2931             :         /* Delete this pair from params array */
    2932           2 :         papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2933             : 
    2934             :         /**
    2935             :          * Case 3: There's database and table name, but no column
    2936             :          * name: Use a default column name and use the table to create
    2937             :          * the dataset
    2938             :          **/
    2939           2 :         nPos = CSLFindName(papszParams, "column");
    2940           2 :         if (nPos == -1)
    2941             :         {
    2942           2 :             *ppszColumn = CPLStrdup(DEFAULT_COLUMN);
    2943             :         }
    2944             :         /**
    2945             :          * Case 4: There's database, table and column name: Use the
    2946             :          * table to create a dataset
    2947             :          **/
    2948             :         else
    2949             :         {
    2950           0 :             *ppszColumn =
    2951           0 :                 CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    2952             : 
    2953             :             /* Delete this pair from params array */
    2954           0 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2955             :         }
    2956             : 
    2957             :         /* Get the rest of the parameters, if exist */
    2958           2 :         nPos = CSLFindName(papszParams, "schema");
    2959           2 :         if (nPos == -1)
    2960             :         {
    2961           0 :             *ppszSchema = CPLStrdup(DEFAULT_SCHEMA);
    2962             :         }
    2963             :         else
    2964             :         {
    2965           2 :             *ppszSchema =
    2966           2 :                 CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    2967             : 
    2968             :             /* Delete this pair from params array */
    2969           2 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2970             :         }
    2971             : 
    2972           2 :         nPos = CSLFindName(papszParams, "where");
    2973           2 :         if (nPos != -1)
    2974             :         {
    2975           0 :             *ppszWhere =
    2976           0 :                 CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    2977             : 
    2978             :             /* Delete this pair from params array */
    2979           0 :             papszParams = CSLRemoveStrings(papszParams, nPos, 1, nullptr);
    2980             :         }
    2981             :     }
    2982             : 
    2983             :     /* Parse ppszWhere, if needed */
    2984           2 :     if (*ppszWhere)
    2985             :     {
    2986             :         pszTmp =
    2987           0 :             ReplaceQuotes(*ppszWhere, static_cast<int>(strlen(*ppszWhere)));
    2988           0 :         CPLFree(*ppszWhere);
    2989           0 :         *ppszWhere = pszTmp;
    2990             :     }
    2991             : 
    2992             :     /***************************************
    2993             :      * Construct a valid connection string
    2994             :      ***************************************/
    2995           2 :     CPLString osConnectionString;
    2996           4 :     for (i = 0; i < CSLCount(papszParams); i++)
    2997             :     {
    2998           2 :         osConnectionString += papszParams[i];
    2999           2 :         osConnectionString += " ";
    3000             :     }
    3001             : 
    3002             :     /**********************************************************
    3003             :      * Set application name if not found in connection string
    3004             :      **********************************************************/
    3005             : 
    3006           2 :     if (*bBrowseDatabase == FALSE && *nMode == ONE_RASTER_PER_TABLE &&
    3007           4 :         CSLFindName(papszParams, "application_name") == -1 &&
    3008           0 :         getenv("PGAPPNAME") == nullptr)
    3009             :     {
    3010           0 :         osConnectionString += "application_name=";
    3011           0 :         osConnectionString += "'";
    3012           0 :         osConnectionString += "GDAL ";
    3013           0 :         osConnectionString += GDALVersionInfo("RELEASE_NAME");
    3014           0 :         osConnectionString += "'";
    3015           0 :         osConnectionString += " ";
    3016             :     }
    3017             : 
    3018           2 :     *ppszConnectionString = CPLStrdup(osConnectionString);
    3019             : 
    3020           2 :     nPos = CSLFindName(papszParams, "host");
    3021           2 :     if (nPos != -1)
    3022             :     {
    3023           0 :         *ppszHost = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    3024             :     }
    3025           2 :     else if (CPLGetConfigOption("PGHOST", nullptr) != nullptr)
    3026             :     {
    3027           2 :         *ppszHost = CPLStrdup(CPLGetConfigOption("PGHOST", nullptr));
    3028             :     }
    3029             :     else
    3030           0 :         *ppszHost = nullptr;
    3031             :     /*else {
    3032             :         CPLError(CE_Failure, CPLE_AppDefined,
    3033             :             "Host parameter must be provided, or PGHOST environment "
    3034             :             "variable must be set. Please set the host and try again.");
    3035             : 
    3036             :         CSLDestroy(papszParams);
    3037             : 
    3038             :         return false;
    3039             :     }*/
    3040             : 
    3041           2 :     nPos = CSLFindName(papszParams, "port");
    3042           2 :     if (nPos != -1)
    3043             :     {
    3044           0 :         *ppszPort = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    3045             :     }
    3046           2 :     else if (CPLGetConfigOption("PGPORT", nullptr) != nullptr)
    3047             :     {
    3048           2 :         *ppszPort = CPLStrdup(CPLGetConfigOption("PGPORT", nullptr));
    3049             :     }
    3050             :     else
    3051           0 :         *ppszPort = nullptr;
    3052             :     /*else {
    3053             :         CPLError(CE_Failure, CPLE_AppDefined,
    3054             :             "Port parameter must be provided, or PGPORT environment "
    3055             :             "variable must be set. Please set the port and try again.");
    3056             : 
    3057             :         CSLDestroy(papszParams);
    3058             : 
    3059             :         return false;
    3060             :     }*/
    3061             : 
    3062           2 :     nPos = CSLFindName(papszParams, "user");
    3063           2 :     if (nPos != -1)
    3064             :     {
    3065           0 :         *ppszUser = CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    3066             :     }
    3067           2 :     else if (CPLGetConfigOption("PGUSER", nullptr) != nullptr)
    3068             :     {
    3069           2 :         *ppszUser = CPLStrdup(CPLGetConfigOption("PGUSER", nullptr));
    3070             :     }
    3071             :     else
    3072           0 :         *ppszUser = nullptr;
    3073             :     /*else {
    3074             :         CPLError(CE_Failure, CPLE_AppDefined,
    3075             :             "User parameter must be provided, or PGUSER environment "
    3076             :             "variable must be set. Please set the user and try again.");
    3077             : 
    3078             :         CSLDestroy(papszParams);
    3079             : 
    3080             :         return false;
    3081             :     }*/
    3082             : 
    3083           2 :     nPos = CSLFindName(papszParams, "password");
    3084           2 :     if (nPos != -1)
    3085             :     {
    3086           0 :         *ppszPassword =
    3087           0 :             CPLStrdup(CPLParseNameValue(papszParams[nPos], nullptr));
    3088             :     }
    3089           2 :     else if (CPLGetConfigOption("PGPASSWORD", nullptr) != nullptr)
    3090             :     {
    3091           2 :         *ppszPassword = CPLStrdup(CPLGetConfigOption("PGPASSWORD", nullptr));
    3092             :     }
    3093             :     else
    3094           0 :         *ppszPassword = nullptr;
    3095             : 
    3096           2 :     CSLDestroy(papszParams);
    3097             : 
    3098             : #ifdef DEBUG_VERBOSE
    3099             :     CPLDebug("PostGIS_Raster",
    3100             :              "PostGISRasterDataset::GetConnectionInfo(): "
    3101             :              "Mode: %d\n"
    3102             :              "Service :%s\n"
    3103             :              "Dbname: %s\n"
    3104             :              "Schema: %s\n"
    3105             :              "Table: %s\n"
    3106             :              "Column: %s\n"
    3107             :              "Where: %s\n"
    3108             :              "Host: %s\n"
    3109             :              "Port: %s\n"
    3110             :              "User: %s\n"
    3111             :              "Password: %s\n"
    3112             :              "Connection String: %s\n",
    3113             :              *nMode, *ppszService ? *ppszService : "(null)",
    3114             :              *ppszDbname ? *ppszDbname : "(null)",
    3115             :              *ppszSchema ? *ppszSchema : "(null)",
    3116             :              *ppszTable ? *ppszTable : "(null)",
    3117             :              *ppszColumn ? *ppszColumn : "(null)",
    3118             :              *ppszWhere ? *ppszWhere : "(null)",
    3119             :              *ppszHost ? *ppszHost : "(null)", *ppszPort ? *ppszPort : "(null)",
    3120             :              *ppszUser ? *ppszUser : "(null)",
    3121             :              *ppszPassword ? *ppszPassword : "(null)", *ppszConnectionString);
    3122             : #endif
    3123             : 
    3124           2 :     return true;
    3125             : }
    3126             : 
    3127             : /***********************************************************************
    3128             :  * \brief Create a connection to a postgres database
    3129             :  **********************************************************************/
    3130           2 : static PGconn *GetConnection(const char *pszFilename,
    3131             :                              char **ppszConnectionString, char **ppszSchema,
    3132             :                              char **ppszTable, char **ppszColumn,
    3133             :                              char **ppszWhere, WorkingMode *nMode,
    3134             :                              GBool *bBrowseDatabase,
    3135             :                              OutDBResolution *peOutDBResolution)
    3136             : {
    3137           2 :     PGconn *poConn = nullptr;
    3138           2 :     char *pszService = nullptr;
    3139           2 :     char *pszDbname = nullptr;
    3140           2 :     char *pszHost = nullptr;
    3141           2 :     char *pszPort = nullptr;
    3142           2 :     char *pszUser = nullptr;
    3143           2 :     char *pszPassword = nullptr;
    3144             : 
    3145           2 :     if (GetConnectionInfo(pszFilename, ppszConnectionString, &pszService,
    3146             :                           &pszDbname, ppszSchema, ppszTable, ppszColumn,
    3147             :                           ppszWhere, &pszHost, &pszPort, &pszUser, &pszPassword,
    3148           2 :                           nMode, bBrowseDatabase, peOutDBResolution))
    3149             :     {
    3150             :         /**************************************************************
    3151             :          * Open a new database connection
    3152             :          **************************************************************/
    3153           2 :         poConn = PostGISRasterDriver::gpoPostGISRasterDriver->GetConnection(
    3154             :             *ppszConnectionString, pszService, pszDbname, pszHost, pszPort,
    3155             :             pszUser);
    3156             : 
    3157           2 :         if (poConn == nullptr)
    3158             :         {
    3159           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3160             :                      "Couldn't establish a database connection");
    3161             :         }
    3162             :     }
    3163             : 
    3164           2 :     CPLFree(pszService);
    3165           2 :     CPLFree(pszDbname);
    3166           2 :     CPLFree(pszHost);
    3167           2 :     CPLFree(pszPort);
    3168           2 :     CPLFree(pszUser);
    3169           2 :     CPLFree(pszPassword);
    3170             : 
    3171           2 :     return poConn;
    3172             : }
    3173             : 
    3174             : /***********************************************************************
    3175             :  * \brief Open a connection with PostgreSQL. The connection string will
    3176             :  * have the PostgreSQL accepted format, plus the next key=value pairs:
    3177             :  *  schema = &lt;schema_name&gt;
    3178             :  *  table = &lt;table_name&gt;
    3179             :  *  column = &lt;column_name&gt;
    3180             :  *  where = &lt;SQL where&gt;
    3181             :  *  mode = &lt;working mode&gt; (1 or 2)
    3182             :  *
    3183             :  * These pairs are used for selecting the right raster table.
    3184             :  **********************************************************************/
    3185           2 : GDALDataset *PostGISRasterDataset::Open(GDALOpenInfo *poOpenInfo)
    3186             : {
    3187           2 :     char *pszConnectionString = nullptr;
    3188           2 :     char *pszSchema = nullptr;
    3189           2 :     char *pszTable = nullptr;
    3190           2 :     char *pszColumn = nullptr;
    3191           2 :     char *pszWhere = nullptr;
    3192           2 :     WorkingMode nMode = NO_MODE;
    3193           2 :     PGconn *poConn = nullptr;
    3194           2 :     PostGISRasterDataset *poDS = nullptr;
    3195           2 :     GBool bBrowseDatabase = false;
    3196             :     OutDBResolution eOutDBResolution;
    3197             : 
    3198             :     /**************************
    3199             :      * Check input parameter
    3200             :      **************************/
    3201           2 :     if (!PostGISRasterDriverIdentify(poOpenInfo))
    3202           0 :         return nullptr;
    3203             : 
    3204           2 :     poConn = GetConnection(poOpenInfo->pszFilename, &pszConnectionString,
    3205             :                            &pszSchema, &pszTable, &pszColumn, &pszWhere, &nMode,
    3206             :                            &bBrowseDatabase, &eOutDBResolution);
    3207           2 :     if (poConn == nullptr)
    3208             :     {
    3209           0 :         CPLFree(pszConnectionString);
    3210           0 :         CPLFree(pszSchema);
    3211           0 :         CPLFree(pszTable);
    3212           0 :         CPLFree(pszColumn);
    3213           0 :         CPLFree(pszWhere);
    3214           0 :         return nullptr;
    3215             :     }
    3216             : 
    3217             :     /* For CLIENT_SIDE_IF_POSSIBLE mode, check if PostGIS 2.5 / ST_BandFileSize
    3218             :      */
    3219             :     /* is available */
    3220           2 :     bool bHasStBandFileSize = false;
    3221           2 :     if (eOutDBResolution == OutDBResolution::CLIENT_SIDE_IF_POSSIBLE)
    3222             :     {
    3223             :         const CPLString osCommand(
    3224           0 :             "SELECT 1 FROM pg_proc WHERE proname = 'st_bandfilesize'");
    3225             : #ifdef DEBUG_QUERY
    3226             :         CPLDebug("PostGIS_Raster", "PostGISRasterDataset::Open(): Query: %s",
    3227             :                  osCommand.c_str());
    3228             : #endif
    3229             : 
    3230           0 :         PGresult *poResult = PQexec(poConn, osCommand);
    3231           0 :         if (poResult && PQresultStatus(poResult) == PGRES_TUPLES_OK &&
    3232           0 :             PQntuples(poResult) == 1)
    3233             :         {
    3234             : #ifdef DEBUG_VERBOSE
    3235             :             CPLDebug("PostGIS_Raster", "ST_BandFileSize available");
    3236             : #endif
    3237           0 :             bHasStBandFileSize = true;
    3238             :         }
    3239           0 :         else if (poResult && PQresultStatus(poResult) != PGRES_TUPLES_OK)
    3240             :         {
    3241           0 :             CPLDebug("PostGIS_Raster", "PostGISRasterDataset::Open(): %s",
    3242             :                      PQerrorMessage(poConn));
    3243             :         }
    3244             : 
    3245           0 :         if (poResult)
    3246           0 :             PQclear(poResult);
    3247             :     }
    3248             : 
    3249             :     /*******************************************************************
    3250             :      * No table will be read. Only shows information about the existent
    3251             :      * raster tables
    3252             :      ******************************************************************/
    3253           2 :     if (bBrowseDatabase)
    3254             :     {
    3255             :         /**
    3256             :          * Creates empty dataset object, only for subdatasets
    3257             :          **/
    3258           0 :         poDS = new PostGISRasterDataset();
    3259           0 :         poDS->poConn = poConn;
    3260           0 :         poDS->eAccess = GA_ReadOnly;
    3261             :         // poDS->poDriver = poDriver;
    3262           0 :         poDS->nMode = (pszSchema) ? BROWSE_SCHEMA : BROWSE_DATABASE;
    3263           0 :         poDS->eOutDBResolution = eOutDBResolution;
    3264           0 :         poDS->bHasStBandFileSize = bHasStBandFileSize;
    3265             : 
    3266             :         /**
    3267             :          * Look for raster tables at database and
    3268             :          * store them as subdatasets
    3269             :          **/
    3270           0 :         if (!poDS->BrowseDatabase(pszSchema, pszConnectionString))
    3271             :         {
    3272           0 :             CPLFree(pszConnectionString);
    3273           0 :             delete poDS;
    3274             : 
    3275           0 :             if (pszSchema)
    3276           0 :                 CPLFree(pszSchema);
    3277           0 :             if (pszTable)
    3278           0 :                 CPLFree(pszTable);
    3279           0 :             if (pszColumn)
    3280           0 :                 CPLFree(pszColumn);
    3281           0 :             if (pszWhere)
    3282           0 :                 CPLFree(pszWhere);
    3283             : 
    3284           0 :             return nullptr;
    3285             :         }
    3286             : 
    3287           0 :         if (pszSchema)
    3288           0 :             CPLFree(pszSchema);
    3289           0 :         if (pszTable)
    3290           0 :             CPLFree(pszTable);
    3291           0 :         if (pszColumn)
    3292           0 :             CPLFree(pszColumn);
    3293           0 :         if (pszWhere)
    3294           0 :             CPLFree(pszWhere);
    3295             :     }
    3296             : 
    3297             :     /*******************************************************************
    3298             :      * A table will be read as dataset: Fetch raster properties from db.
    3299             :      ******************************************************************/
    3300             :     else
    3301             :     {
    3302           2 :         poDS = new PostGISRasterDataset();
    3303           2 :         poDS->poConn = poConn;
    3304           2 :         poDS->eAccess = poOpenInfo->eAccess;
    3305           2 :         poDS->nMode = nMode;
    3306           2 :         poDS->eOutDBResolution = eOutDBResolution;
    3307           2 :         poDS->bHasStBandFileSize = bHasStBandFileSize;
    3308             :         // poDS->poDriver = poDriver;
    3309             : 
    3310           2 :         poDS->pszSchema = pszSchema;
    3311           2 :         poDS->pszTable = pszTable;
    3312           2 :         poDS->pszColumn = pszColumn;
    3313           2 :         poDS->pszWhere = pszWhere;
    3314             : 
    3315             :         /**
    3316             :          * Fetch basic raster metadata from db
    3317             :          **/
    3318             : #ifdef DEBUG_VERBOSE
    3319             :         CPLDebug("PostGIS_Raster", "Open:: connection string = %s",
    3320             :                  pszConnectionString);
    3321             : #endif
    3322             : 
    3323           2 :         if (!poDS->SetRasterProperties(pszConnectionString))
    3324             :         {
    3325           2 :             CPLFree(pszConnectionString);
    3326           2 :             delete poDS;
    3327           2 :             return nullptr;
    3328             :         }
    3329             :     }
    3330             : 
    3331           0 :     CPLFree(pszConnectionString);
    3332           0 :     return poDS;
    3333             : }
    3334             : 
    3335             : /************************************************************************/
    3336             : /*                      GetMetadataDomainList()                         */
    3337             : /************************************************************************/
    3338             : 
    3339           0 : char **PostGISRasterDataset::GetMetadataDomainList()
    3340             : {
    3341           0 :     return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE,
    3342           0 :                                    "SUBDATASETS", nullptr);
    3343             : }
    3344             : 
    3345             : /*****************************************
    3346             :  * \brief Get Metadata from raster
    3347             :  * TODO: Add more options (the result of
    3348             :  * calling ST_Metadata, for example)
    3349             :  *****************************************/
    3350           0 : char **PostGISRasterDataset::GetMetadata(const char *pszDomain)
    3351             : {
    3352           0 :     if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS"))
    3353           0 :         return papszSubdatasets;
    3354             :     else
    3355           0 :         return GDALDataset::GetMetadata(pszDomain);
    3356             : }
    3357             : 
    3358             : /*****************************************************
    3359             :  * \brief Fetch the projection definition string
    3360             :  * for this dataset in OpenGIS WKT format. It should
    3361             :  * be suitable for use with the OGRSpatialReference
    3362             :  * class.
    3363             :  *****************************************************/
    3364           0 : const OGRSpatialReference *PostGISRasterDataset::GetSpatialRef() const
    3365             : {
    3366             : 
    3367           0 :     if (nSrid == -1)
    3368           0 :         return nullptr;
    3369             : 
    3370           0 :     if (!m_oSRS.IsEmpty())
    3371           0 :         return &m_oSRS;
    3372             : 
    3373             :     /********************************************************
    3374             :      *          Reading proj from database
    3375             :      ********************************************************/
    3376           0 :     CPLString osCommand;
    3377           0 :     osCommand.Printf("SELECT srtext FROM spatial_ref_sys where SRID=%d", nSrid);
    3378           0 :     PGresult *poResult = PQexec(this->poConn, osCommand.c_str());
    3379           0 :     if (poResult && PQresultStatus(poResult) == PGRES_TUPLES_OK &&
    3380           0 :         PQntuples(poResult) > 0)
    3381             :     {
    3382           0 :         const char *pszProjection = PQgetvalue(poResult, 0, 0);
    3383           0 :         if (pszProjection && pszProjection[0])
    3384           0 :             m_oSRS.importFromWkt(pszProjection);
    3385             :     }
    3386             : 
    3387           0 :     if (poResult)
    3388           0 :         PQclear(poResult);
    3389             : 
    3390           0 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
    3391             : }
    3392             : 
    3393             : /**********************************************************
    3394             :  * \brief Set projection definition. The input string must
    3395             :  * be in OGC WKT or PROJ.4 format
    3396             :  **********************************************************/
    3397           0 : CPLErr PostGISRasterDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
    3398             : {
    3399             : 
    3400           0 :     if (poSRS == nullptr)
    3401           0 :         return CE_None;
    3402             : 
    3403           0 :     CPLString osCommand;
    3404             : 
    3405             :     /*****************************************************************
    3406             :      * Check if the dataset allows updating
    3407             :      *****************************************************************/
    3408           0 :     if (GetAccess() != GA_Update)
    3409             :     {
    3410           0 :         ReportError(CE_Failure, CPLE_NoWriteAccess,
    3411             :                     "This driver doesn't allow write access");
    3412           0 :         return CE_Failure;
    3413             :     }
    3414             : 
    3415             :     /*****************************************************************
    3416             :      * Look for projection with this text
    3417             :      *****************************************************************/
    3418             : 
    3419           0 :     char *pszWKT = nullptr;
    3420           0 :     poSRS->exportToWkt(&pszWKT);
    3421           0 :     if (pszWKT == nullptr)
    3422           0 :         return CE_Failure;
    3423             : 
    3424           0 :     osCommand.Printf("SELECT srid FROM spatial_ref_sys where srtext='%s'",
    3425           0 :                      pszWKT);
    3426           0 :     CPLFree(pszWKT);
    3427           0 :     PGresult *poResult = PQexec(poConn, osCommand.c_str());
    3428             : 
    3429           0 :     if (poResult && PQresultStatus(poResult) == PGRES_TUPLES_OK &&
    3430           0 :         PQntuples(poResult) > 0)
    3431             :     {
    3432             : 
    3433           0 :         const int nFetchedSrid = atoi(PQgetvalue(poResult, 0, 0));
    3434             : 
    3435             :         // update class attribute
    3436           0 :         nSrid = nFetchedSrid;
    3437             : 
    3438             :         // update raster_columns table
    3439             :         osCommand.Printf("UPDATE raster_columns SET srid=%d WHERE \
    3440             :                     r_table_name = '%s' AND r_column = '%s'",
    3441           0 :                          nSrid, pszTable, pszColumn);
    3442           0 :         poResult = PQexec(poConn, osCommand.c_str());
    3443           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3444             :         {
    3445           0 :             ReportError(CE_Failure, CPLE_AppDefined,
    3446             :                         "Couldn't update raster_columns table: %s",
    3447           0 :                         PQerrorMessage(poConn));
    3448           0 :             return CE_Failure;
    3449             :         }
    3450             : 
    3451             :         // TODO: Update ALL blocks with the new srid...
    3452             : 
    3453           0 :         return CE_None;
    3454             :     }
    3455             :     else
    3456             :     {
    3457           0 :         ReportError(CE_Failure, CPLE_WrongFormat,
    3458             :                     "Couldn't find WKT definition");
    3459           0 :         return CE_Failure;
    3460             :     }
    3461             : }
    3462             : 
    3463             : /********************************************************
    3464             :  * \brief Set the affine transformation coefficients
    3465             :  ********************************************************/
    3466           0 : CPLErr PostGISRasterDataset::SetGeoTransform(double *padfGeoTransform)
    3467             : {
    3468           0 :     if (!padfGeoTransform)
    3469           0 :         return CE_Failure;
    3470             : 
    3471           0 :     memcpy(adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
    3472             : 
    3473           0 :     return CE_None;
    3474             : }
    3475             : 
    3476             : /********************************************************
    3477             :  * \brief Get the affine transformation coefficients
    3478             :  ********************************************************/
    3479           0 : CPLErr PostGISRasterDataset::GetGeoTransform(double *padfGeoTransform)
    3480             : {
    3481             : 
    3482             :     // copy necessary values in supplied buffer
    3483           0 :     memcpy(padfGeoTransform, adfGeoTransform, 6 * sizeof(double));
    3484             : 
    3485           0 :     if (nRasterXSize == 0 && nRasterYSize == 0)
    3486           0 :         return CE_Failure;
    3487             : 
    3488             :     /* To avoid QGIS trying to create a warped VRT for what is really */
    3489             :     /* an ungeoreferenced dataset */
    3490           0 :     if (CPLIsEqual(padfGeoTransform[0], 0.0) &&
    3491           0 :         CPLIsEqual(padfGeoTransform[1], 1.0) &&
    3492           0 :         CPLIsEqual(padfGeoTransform[2], 0.0) &&
    3493           0 :         CPLIsEqual(padfGeoTransform[3], 0.0) &&
    3494           0 :         CPLIsEqual(padfGeoTransform[4], 0.0) &&
    3495           0 :         CPLIsEqual(padfGeoTransform[5], 1.0))
    3496             :     {
    3497           0 :         return CE_Failure;
    3498             :     }
    3499             : 
    3500           0 :     return CE_None;
    3501             : }
    3502             : 
    3503             : /*********************************************************
    3504             :  * \brief Fetch files forming dataset.
    3505             :  *
    3506             :  * We need to define this method because the VRTDataset
    3507             :  * method doesn't check for NULL FileList before trying
    3508             :  * to collect the names of all sources' file list.
    3509             :  *********************************************************/
    3510           0 : char **PostGISRasterDataset::GetFileList()
    3511             : {
    3512           0 :     return nullptr;
    3513             : }
    3514             : 
    3515             : /********************************************************
    3516             :  * \brief Create a copy of a PostGIS Raster dataset.
    3517             :  ********************************************************/
    3518          18 : GDALDataset *PostGISRasterDataset::CreateCopy(
    3519             :     CPL_UNUSED const char *pszFilename, GDALDataset *poGSrcDS,
    3520             :     CPL_UNUSED int bStrict, CPL_UNUSED char **papszOptions,
    3521             :     CPL_UNUSED GDALProgressFunc pfnProgress, CPL_UNUSED void *pProgressData)
    3522             : {
    3523          18 :     char *pszSchema = nullptr;
    3524          18 :     char *pszTable = nullptr;
    3525          18 :     char *pszColumn = nullptr;
    3526          18 :     char *pszWhere = nullptr;
    3527          18 :     GBool bBrowseDatabase = false;
    3528             :     WorkingMode nMode;
    3529             :     OutDBResolution eOutDBResolution;
    3530          18 :     char *pszConnectionString = nullptr;
    3531          18 :     PGconn *poConn = nullptr;
    3532          18 :     PGresult *poResult = nullptr;
    3533             :     GBool bInsertSuccess;
    3534             : 
    3535          36 :     CPLString osCommand;
    3536             : 
    3537          18 :     if (poGSrcDS->GetDriver() != GDALGetDriverByName("PostGISRaster"))
    3538             :     {
    3539          18 :         CPLError(CE_Failure, CPLE_NotSupported,
    3540             :                  "PostGISRasterDataset::CreateCopy() only works on source "
    3541             :                  "datasets that are PostGISRaster");
    3542          18 :         return nullptr;
    3543             :     }
    3544             : 
    3545             :     // Now we can do the cast
    3546             :     PostGISRasterDataset *poSrcDS =
    3547           0 :         cpl::down_cast<PostGISRasterDataset *>(poGSrcDS);
    3548             : 
    3549             :     // Check connection string
    3550           0 :     if (pszFilename == nullptr || !STARTS_WITH_CI(pszFilename, "PG:"))
    3551             :     {
    3552             :         /**
    3553             :          * The connection string provided is not a valid connection
    3554             :          * string.
    3555             :          */
    3556           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3557             :                  "PostGIS Raster driver was unable to parse the provided "
    3558             :                  "connection string.");
    3559           0 :         return nullptr;
    3560             :     }
    3561             : 
    3562           0 :     poConn = GetConnection(pszFilename, &pszConnectionString, &pszSchema,
    3563             :                            &pszTable, &pszColumn, &pszWhere, &nMode,
    3564             :                            &bBrowseDatabase, &eOutDBResolution);
    3565           0 :     if (poConn == nullptr || bBrowseDatabase || pszTable == nullptr)
    3566             :     {
    3567           0 :         CPLFree(pszConnectionString);
    3568           0 :         CPLFree(pszSchema);
    3569           0 :         CPLFree(pszTable);
    3570           0 :         CPLFree(pszColumn);
    3571           0 :         CPLFree(pszWhere);
    3572             : 
    3573             :         // if connection info fails, browsing mode, or no table set
    3574           0 :         return nullptr;
    3575             :     }
    3576             : 
    3577           0 :     CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    3578           0 :     CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
    3579           0 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    3580             : 
    3581             :     // begin transaction
    3582           0 :     poResult = PQexec(poConn, "begin");
    3583           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3584             :     {
    3585           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3586             :                  "Error beginning database transaction: %s",
    3587             :                  PQerrorMessage(poConn));
    3588           0 :         if (poResult != nullptr)
    3589           0 :             PQclear(poResult);
    3590           0 :         CPLFree(pszSchema);
    3591           0 :         CPLFree(pszTable);
    3592           0 :         CPLFree(pszColumn);
    3593           0 :         CPLFree(pszWhere);
    3594             : 
    3595           0 :         CPLFree(pszConnectionString);
    3596             : 
    3597           0 :         return nullptr;
    3598             :     }
    3599             : 
    3600           0 :     PQclear(poResult);
    3601             : 
    3602             :     // create table for raster (if not exists because a
    3603             :     // dataset will not be reported for an empty table)
    3604             : 
    3605             :     // TODO: is 'rid' necessary?
    3606           0 :     osCommand.Printf("create table if not exists %s.%s (rid serial, %s "
    3607             :                      "raster, constraint %s_pkey primary key (rid));",
    3608           0 :                      pszSchema, pszTable, pszColumn, pszTable);
    3609           0 :     poResult = PQexec(poConn, osCommand.c_str());
    3610           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3611             :     {
    3612             : 
    3613           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3614             :                  "Error creating needed tables: %s", PQerrorMessage(poConn));
    3615           0 :         if (poResult != nullptr)
    3616           0 :             PQclear(poResult);
    3617             : 
    3618             :         // rollback
    3619           0 :         poResult = PQexec(poConn, "rollback");
    3620           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3621             :         {
    3622             : 
    3623           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3624             :                      "Error rolling back transaction: %s",
    3625             :                      PQerrorMessage(poConn));
    3626             :         }
    3627           0 :         if (poResult != nullptr)
    3628           0 :             PQclear(poResult);
    3629           0 :         if (pszSchema)
    3630           0 :             CPLFree(pszSchema);
    3631           0 :         if (pszTable)
    3632           0 :             CPLFree(pszTable);
    3633           0 :         if (pszColumn)
    3634           0 :             CPLFree(pszColumn);
    3635           0 :         if (pszWhere)
    3636           0 :             CPLFree(pszWhere);
    3637             : 
    3638           0 :         CPLFree(pszConnectionString);
    3639             : 
    3640           0 :         return nullptr;
    3641             :     }
    3642             : 
    3643           0 :     PQclear(poResult);
    3644             : 
    3645           0 :     CPLString osIdxNameI;
    3646           0 :     osIdxNameI.Printf("%s_%s_gist", pszTable, pszColumn);
    3647           0 :     osIdxNameI = CPLQuotedSQLIdentifier(osIdxNameI);
    3648             : 
    3649             :     osCommand.Printf("create index %s ON %s.%s USING gist "
    3650             :                      "(st_convexhull(%s));",
    3651             :                      osIdxNameI.c_str(), osSchemaI.c_str(), osTableI.c_str(),
    3652           0 :                      osColumnI.c_str());
    3653           0 :     poResult = PQexec(poConn, osCommand.c_str());
    3654           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3655             :     {
    3656             : 
    3657           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error creating needed index: %s",
    3658             :                  PQerrorMessage(poConn));
    3659           0 :         if (poResult != nullptr)
    3660           0 :             PQclear(poResult);
    3661             : 
    3662             :         // rollback
    3663           0 :         poResult = PQexec(poConn, "rollback");
    3664           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3665             :         {
    3666             : 
    3667           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3668             :                      "Error rolling back transaction: %s",
    3669             :                      PQerrorMessage(poConn));
    3670             :         }
    3671           0 :         if (poResult != nullptr)
    3672           0 :             PQclear(poResult);
    3673           0 :         if (pszSchema)
    3674           0 :             CPLFree(pszSchema);
    3675           0 :         if (pszTable)
    3676           0 :             CPLFree(pszTable);
    3677           0 :         if (pszColumn)
    3678           0 :             CPLFree(pszColumn);
    3679           0 :         if (pszWhere)
    3680           0 :             CPLFree(pszWhere);
    3681             : 
    3682           0 :         CPLFree(pszConnectionString);
    3683             : 
    3684           0 :         return nullptr;
    3685             :     }
    3686             : 
    3687           0 :     PQclear(poResult);
    3688             : 
    3689           0 :     const char *pszSubdatasetName = nullptr;
    3690           0 :     PostGISRasterDataset *poSubDS = nullptr;
    3691           0 :     if (poSrcDS->nMode == ONE_RASTER_PER_TABLE)
    3692             :     {
    3693             :         // one raster per table
    3694             : 
    3695             :         // insert one raster
    3696             :         bInsertSuccess =
    3697           0 :             InsertRaster(poConn, poSrcDS, pszSchema, pszTable, pszColumn);
    3698           0 :         if (!bInsertSuccess)
    3699             :         {
    3700             :             // rollback
    3701           0 :             poResult = PQexec(poConn, "rollback");
    3702           0 :             if (poResult == nullptr ||
    3703           0 :                 PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3704             :             {
    3705             : 
    3706           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    3707             :                          "Error rolling back transaction: %s",
    3708             :                          PQerrorMessage(poConn));
    3709             :             }
    3710           0 :             if (poResult != nullptr)
    3711           0 :                 PQclear(poResult);
    3712           0 :             if (pszSchema)
    3713           0 :                 CPLFree(pszSchema);
    3714           0 :             if (pszTable)
    3715           0 :                 CPLFree(pszTable);
    3716           0 :             if (pszColumn)
    3717           0 :                 CPLFree(pszColumn);
    3718           0 :             if (pszWhere)
    3719           0 :                 CPLFree(pszWhere);
    3720             : 
    3721           0 :             CPLFree(pszConnectionString);
    3722             : 
    3723           0 :             return nullptr;
    3724             :         }
    3725             :     }
    3726           0 :     else if (poSrcDS->nMode == ONE_RASTER_PER_ROW)
    3727             :     {
    3728             :         // one raster per row
    3729             : 
    3730             :         // papszSubdatasets contains name/desc for each subdataset
    3731           0 :         for (int i = 0; i < CSLCount(poSrcDS->papszSubdatasets); i += 2)
    3732             :         {
    3733             :             pszSubdatasetName =
    3734           0 :                 CPLParseNameValue(poSrcDS->papszSubdatasets[i], nullptr);
    3735           0 :             if (pszSubdatasetName == nullptr)
    3736             :             {
    3737           0 :                 CPLDebug("PostGIS_Raster",
    3738             :                          "PostGISRasterDataset::CreateCopy(): "
    3739             :                          "Could not parse name/value out of subdataset list: "
    3740             :                          "%s",
    3741           0 :                          poSrcDS->papszSubdatasets[i]);
    3742           0 :                 continue;
    3743             :             }
    3744             : 
    3745             :             // for each subdataset
    3746           0 :             GDALOpenInfo poOpenInfo(pszSubdatasetName, GA_ReadOnly);
    3747             :             // open the subdataset
    3748           0 :             poSubDS = cpl::down_cast<PostGISRasterDataset *>(Open(&poOpenInfo));
    3749             : 
    3750           0 :             if (poSubDS == nullptr)
    3751             :             {
    3752             :                 // notify!
    3753           0 :                 CPLDebug("PostGIS_Raster",
    3754             :                          "PostGISRasterDataset::CreateCopy(): "
    3755             :                          "Could not open a subdataset: %s",
    3756             :                          pszSubdatasetName);
    3757           0 :                 continue;
    3758             :             }
    3759             : 
    3760             :             // insert one raster
    3761             :             bInsertSuccess =
    3762           0 :                 InsertRaster(poConn, poSubDS, pszSchema, pszTable, pszColumn);
    3763             : 
    3764           0 :             if (!bInsertSuccess)
    3765             :             {
    3766           0 :                 CPLDebug("PostGIS_Raster",
    3767             :                          "PostGISRasterDataset::CreateCopy(): "
    3768             :                          "Could not copy raster subdataset to new dataset.");
    3769             : 
    3770             :                 // keep trying ...
    3771             :             }
    3772             : 
    3773             :             // close this dataset
    3774           0 :             GDALClose(GDALDataset::ToHandle(poSubDS));
    3775             :         }
    3776             :     }
    3777             : 
    3778             :     // commit transaction
    3779           0 :     poResult = PQexec(poConn, "commit");
    3780           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3781             :     {
    3782           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3783             :                  "Error committing database transaction: %s",
    3784             :                  PQerrorMessage(poConn));
    3785           0 :         if (poResult != nullptr)
    3786           0 :             PQclear(poResult);
    3787           0 :         if (pszSchema)
    3788           0 :             CPLFree(pszSchema);
    3789           0 :         if (pszTable)
    3790           0 :             CPLFree(pszTable);
    3791           0 :         if (pszColumn)
    3792           0 :             CPLFree(pszColumn);
    3793           0 :         if (pszWhere)
    3794           0 :             CPLFree(pszWhere);
    3795             : 
    3796           0 :         CPLFree(pszConnectionString);
    3797             : 
    3798           0 :         return nullptr;
    3799             :     }
    3800             : 
    3801           0 :     PQclear(poResult);
    3802             : 
    3803           0 :     if (pszSchema)
    3804           0 :         CPLFree(pszSchema);
    3805           0 :     if (pszTable)
    3806           0 :         CPLFree(pszTable);
    3807           0 :     if (pszColumn)
    3808           0 :         CPLFree(pszColumn);
    3809           0 :     if (pszWhere)
    3810           0 :         CPLFree(pszWhere);
    3811             : 
    3812           0 :     CPLFree(pszConnectionString);
    3813             : 
    3814           0 :     CPLDebug("PostGIS_Raster",
    3815             :              "PostGISRasterDataset::CreateCopy(): "
    3816             :              "Opening new dataset: %s",
    3817             :              pszFilename);
    3818             : 
    3819             :     // connect to the new dataset
    3820           0 :     GDALOpenInfo poOpenInfo(pszFilename, GA_Update);
    3821             :     // open the newdataset
    3822           0 :     poSubDS = cpl::down_cast<PostGISRasterDataset *>(Open(&poOpenInfo));
    3823             : 
    3824           0 :     if (poSubDS == nullptr)
    3825             :     {
    3826           0 :         CPLDebug("PostGIS_Raster", "PostGISRasterDataset::CreateCopy(): "
    3827             :                                    "New dataset could not be opened.");
    3828             :     }
    3829             : 
    3830           0 :     return poSubDS;
    3831             : }
    3832             : 
    3833             : /********************************************************
    3834             :  * \brief Helper method to insert a new raster.
    3835             :  ********************************************************/
    3836           0 : GBool PostGISRasterDataset::InsertRaster(PGconn *poConn,
    3837             :                                          PostGISRasterDataset *poSrcDS,
    3838             :                                          const char *pszSchema,
    3839             :                                          const char *pszTable,
    3840             :                                          const char *pszColumn)
    3841             : {
    3842           0 :     CPLString osCommand;
    3843           0 :     PGresult *poResult = nullptr;
    3844             : 
    3845           0 :     CPLString osSchemaI(CPLQuotedSQLIdentifier(pszSchema));
    3846           0 :     CPLString osTableI(CPLQuotedSQLIdentifier(pszTable));
    3847           0 :     CPLString osColumnI(CPLQuotedSQLIdentifier(pszColumn));
    3848           0 :     CPLString osSrcSchemaI(CPLQuotedSQLIdentifier(poSrcDS->pszSchema));
    3849           0 :     CPLString osSrcTableI(CPLQuotedSQLIdentifier(poSrcDS->pszTable));
    3850           0 :     CPLString osSrcColumnI(CPLQuotedSQLIdentifier(poSrcDS->pszColumn));
    3851             : 
    3852           0 :     if (poSrcDS->pszWhere == nullptr)
    3853             :     {
    3854             :         osCommand.Printf("insert into %s.%s (%s) (select %s from %s.%s)",
    3855             :                          osSchemaI.c_str(), osTableI.c_str(), osColumnI.c_str(),
    3856             :                          osSrcColumnI.c_str(), osSrcSchemaI.c_str(),
    3857           0 :                          osSrcTableI.c_str());
    3858             :     }
    3859             :     else
    3860             :     {
    3861             :         osCommand.Printf(
    3862             :             "insert into %s.%s (%s) (select %s from %s.%s where %s)",
    3863             :             osSchemaI.c_str(), osTableI.c_str(), osColumnI.c_str(),
    3864             :             osSrcColumnI.c_str(), osSrcSchemaI.c_str(), osSrcTableI.c_str(),
    3865           0 :             poSrcDS->pszWhere);
    3866             :     }
    3867             : 
    3868             : #ifdef DEBUG_QUERY
    3869             :     CPLDebug("PostGIS_Raster",
    3870             :              "PostGISRasterDataset::InsertRaster(): Query = %s",
    3871             :              osCommand.c_str());
    3872             : #endif
    3873             : 
    3874           0 :     poResult = PQexec(poConn, osCommand.c_str());
    3875           0 :     if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3876             :     {
    3877             : 
    3878           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Error inserting raster: %s",
    3879             :                  PQerrorMessage(poConn));
    3880           0 :         if (poResult != nullptr)
    3881           0 :             PQclear(poResult);
    3882             : 
    3883           0 :         return false;
    3884             :     }
    3885             : 
    3886           0 :     PQclear(poResult);
    3887             : 
    3888           0 :     return true;
    3889             : }
    3890             : 
    3891             : /*********************************************************
    3892             :  * \brief Delete a PostGIS Raster dataset.
    3893             :  *********************************************************/
    3894           0 : CPLErr PostGISRasterDataset::Delete(const char *pszFilename)
    3895             : {
    3896           0 :     char *pszSchema = nullptr;
    3897           0 :     char *pszTable = nullptr;
    3898           0 :     char *pszColumn = nullptr;
    3899           0 :     char *pszWhere = nullptr;
    3900             :     GBool bBrowseDatabase;
    3901           0 :     char *pszConnectionString = nullptr;
    3902             :     WorkingMode nMode;
    3903             :     OutDBResolution eOutDBResolution;
    3904           0 :     PGconn *poConn = nullptr;
    3905           0 :     CPLString osCommand;
    3906           0 :     CPLErr nError = CE_Failure;
    3907             : 
    3908             :     // Check connection string
    3909           0 :     if (pszFilename == nullptr || !STARTS_WITH_CI(pszFilename, "PG:"))
    3910             :     {
    3911             :         /**
    3912             :          * The connection string provided is not a valid connection
    3913             :          * string.
    3914             :          */
    3915           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    3916             :                  "PostGIS Raster driver was unable to parse the provided "
    3917             :                  "connection string. Nothing was deleted.");
    3918           0 :         return CE_Failure;
    3919             :     }
    3920             : 
    3921           0 :     poConn = GetConnection(pszFilename, &pszConnectionString, &pszSchema,
    3922             :                            &pszTable, &pszColumn, &pszWhere, &nMode,
    3923             :                            &bBrowseDatabase, &eOutDBResolution);
    3924           0 :     if (poConn == nullptr || pszSchema == nullptr || pszTable == nullptr)
    3925             :     {
    3926           0 :         CPLFree(pszConnectionString);
    3927           0 :         CPLFree(pszSchema);
    3928           0 :         CPLFree(pszTable);
    3929           0 :         CPLFree(pszColumn);
    3930           0 :         CPLFree(pszWhere);
    3931             : 
    3932           0 :         return CE_Failure;
    3933             :     }
    3934             : 
    3935             :     // begin transaction
    3936             :     {
    3937           0 :         PGresult *poResult = PQexec(poConn, "begin");
    3938           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3939             :         {
    3940           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3941             :                      "Error beginning database transaction: %s",
    3942             :                      PQerrorMessage(poConn));
    3943             : 
    3944             :             // set nMode to NO_MODE to avoid any further processing
    3945           0 :             nMode = NO_MODE;
    3946             :         }
    3947             : 
    3948           0 :         PQclear(poResult);
    3949             :     }
    3950             : 
    3951           0 :     if (nMode == ONE_RASTER_PER_TABLE ||
    3952           0 :         (nMode == ONE_RASTER_PER_ROW && pszWhere == nullptr))
    3953             :     {
    3954             :         // without a where clause, this delete command shall delete
    3955             :         // all subdatasets, even if the mode is ONE_RASTER_PER_ROW
    3956             : 
    3957             :         // drop table <schema>.<table>;
    3958           0 :         osCommand.Printf("drop table %s.%s", pszSchema, pszTable);
    3959           0 :         PGresult *poResult = PQexec(poConn, osCommand.c_str());
    3960           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3961             :         {
    3962           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3963             :                      "Couldn't drop the table %s.%s: %s", pszSchema, pszTable,
    3964             :                      PQerrorMessage(poConn));
    3965             :         }
    3966             :         else
    3967             :         {
    3968           0 :             nError = CE_None;
    3969             :         }
    3970           0 :         if (poResult)
    3971           0 :             PQclear(poResult);
    3972             :     }
    3973           0 :     else if (nMode == ONE_RASTER_PER_ROW)
    3974             :     {
    3975             : 
    3976             :         // delete from <schema>.<table> where <where>
    3977           0 :         osCommand.Printf("delete from %s.%s where %s", pszSchema, pszTable,
    3978           0 :                          pszWhere);
    3979           0 :         PGresult *poResult = PQexec(poConn, osCommand.c_str());
    3980           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    3981             :         {
    3982           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    3983             :                      "Couldn't delete records from the table %s.%s: %s",
    3984             :                      pszSchema, pszTable, PQerrorMessage(poConn));
    3985             :         }
    3986             :         else
    3987             :         {
    3988           0 :             nError = CE_None;
    3989             :         }
    3990           0 :         if (poResult)
    3991           0 :             PQclear(poResult);
    3992             :     }
    3993             : 
    3994             :     // if mode == NO_MODE, the begin transaction above did not complete,
    3995             :     // so no commit is necessary
    3996           0 :     if (nMode != NO_MODE)
    3997             :     {
    3998           0 :         PGresult *poResult = PQexec(poConn, "commit");
    3999           0 :         if (poResult == nullptr || PQresultStatus(poResult) != PGRES_COMMAND_OK)
    4000             :         {
    4001             : 
    4002           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4003             :                      "Error committing database transaction: %s",
    4004             :                      PQerrorMessage(poConn));
    4005             : 
    4006           0 :             nError = CE_Failure;
    4007             :         }
    4008           0 :         if (poResult)
    4009           0 :             PQclear(poResult);
    4010             :     }
    4011             : 
    4012           0 :     CPLFree(pszSchema);
    4013           0 :     CPLFree(pszTable);
    4014           0 :     CPLFree(pszColumn);
    4015           0 :     CPLFree(pszWhere);
    4016             : 
    4017             :     // clean up connection string
    4018           0 :     CPLFree(pszConnectionString);
    4019             : 
    4020           0 :     return nError;
    4021             : }
    4022             : 
    4023             : /***********************************************************************
    4024             :  * \brief Create an array with all the coordinates needed to construct
    4025             :  * a polygon using ST_PolygonFromText.
    4026             :  **********************************************************************/
    4027           0 : GBool PostGISRasterDataset::PolygonFromCoords(int nXOff, int nYOff,
    4028             :                                               int nXEndOff, int nYEndOff,
    4029             :                                               double adfProjWin[8])
    4030             : {
    4031             :     // We first construct a polygon to intersect with
    4032           0 :     int ulx = nXOff;
    4033           0 :     int uly = nYOff;
    4034           0 :     int lrx = nXEndOff;
    4035           0 :     int lry = nYEndOff;
    4036             : 
    4037           0 :     double xRes = adfGeoTransform[GEOTRSFRM_WE_RES];
    4038           0 :     double yRes = adfGeoTransform[GEOTRSFRM_NS_RES];
    4039             : 
    4040           0 :     adfProjWin[0] = adfGeoTransform[GEOTRSFRM_TOPLEFT_X] + ulx * xRes +
    4041           0 :                     uly * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1];
    4042           0 :     adfProjWin[1] = adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    4043           0 :                     ulx * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] +
    4044           0 :                     uly * yRes;
    4045           0 :     adfProjWin[2] = adfGeoTransform[GEOTRSFRM_TOPLEFT_X] + lrx * xRes +
    4046           0 :                     uly * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1];
    4047           0 :     adfProjWin[3] = adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    4048           0 :                     lrx * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] +
    4049           0 :                     uly * yRes;
    4050           0 :     adfProjWin[4] = adfGeoTransform[GEOTRSFRM_TOPLEFT_X] + lrx * xRes +
    4051           0 :                     lry * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1];
    4052           0 :     adfProjWin[5] = adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    4053           0 :                     lrx * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] +
    4054           0 :                     lry * yRes;
    4055           0 :     adfProjWin[6] = adfGeoTransform[GEOTRSFRM_TOPLEFT_X] + ulx * xRes +
    4056           0 :                     lry * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM1];
    4057           0 :     adfProjWin[7] = adfGeoTransform[GEOTRSFRM_TOPLEFT_Y] +
    4058           0 :                     ulx * adfGeoTransform[GEOTRSFRM_ROTATION_PARAM2] +
    4059           0 :                     lry * yRes;
    4060             : 
    4061             : #ifdef DEBUG_VERBOSE
    4062             :     CPLDebug("PostGIS_Raster",
    4063             :              "PostGISRasterDataset::PolygonFromCoords: constructed "
    4064             :              "polygon: POLYGON((%.17f %.17f, %.17f %.17f, %.17f %.17f, "
    4065             :              "%.17f %.17f, %.17f %.17f))",
    4066             :              adfProjWin[0], adfProjWin[1], adfProjWin[2], adfProjWin[3],
    4067             :              adfProjWin[4], adfProjWin[5], adfProjWin[6], adfProjWin[7],
    4068             :              adfProjWin[0], adfProjWin[1]);
    4069             : #endif
    4070             : 
    4071           0 :     return true;
    4072             : }
    4073             : 
    4074             : /***********************************************************************
    4075             :  * GDALRegister_PostGISRaster()
    4076             :  **********************************************************************/
    4077           7 : void GDALRegister_PostGISRaster()
    4078             : 
    4079             : {
    4080           7 :     if (!GDAL_CHECK_VERSION("PostGISRaster driver"))
    4081           0 :         return;
    4082             : 
    4083           7 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
    4084           0 :         return;
    4085             : 
    4086           7 :     GDALDriver *poDriver = new PostGISRasterDriver();
    4087           7 :     PostGISRasterDriverSetCommonMetadata(poDriver);
    4088             : 
    4089           7 :     poDriver->pfnOpen = PostGISRasterDataset::Open;
    4090           7 :     poDriver->pfnCreateCopy = PostGISRasterDataset::CreateCopy;
    4091           7 :     poDriver->pfnDelete = PostGISRasterDataset::Delete;
    4092             : 
    4093           7 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    4094             : }

Generated by: LCOV version 1.14