• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MerginMaps / geodiff / 20338716598

18 Dec 2025 01:34PM UTC coverage: 88.019% (-0.3%) from 88.298%
20338716598

push

github

wonder-sk
Fix memory leaks on Postgres connection failure

0 of 3 new or added lines in 1 file covered. (0.0%)

90 existing lines in 2 files now uncovered.

3607 of 4098 relevant lines covered (88.02%)

577.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.18
/geodiff/src/drivers/postgresdriver.cpp
1
/*
2
 GEODIFF - MIT License
3
 Copyright (C) 2020 Martin Dobias
4
*/
5

6
#include "postgresdriver.h"
7

8
#include "driver.h"
9
#include "geodifflogger.hpp"
10
#include "geodiffutils.hpp"
11
#include "changeset.h"
12
#include "changesetreader.h"
13
#include "changesetutils.h"
14
#include "changesetwriter.h"
15
#include "postgresutils.h"
16
#include "sqliteutils.h"
17
#include "geodiffcontext.hpp"
18

19
#include <algorithm>
20
#include <iostream>
21
#include <memory.h>
22

23

24
/**
25
 * Wrapper around PostgreSQL transactions.
26
 *
27
 * Constructor start a trasaction, it needs to be confirmed by a call to commitChanges() when
28
 * changes are ready to be written. If commitChanges() is not called, changes since the constructor
29
 * will be rolled back (so that on exception everything gets cleaned up properly).
30
 */
31
class PostgresTransaction
32
{
33
  public:
34
    explicit PostgresTransaction( PGconn *conn )
24✔
35
      : mConn( conn )
24✔
36
    {
37
      execSql( mConn, "BEGIN" );
24✔
38
    }
24✔
39

40
    ~PostgresTransaction()
24✔
41
    {
42
      if ( mConn )
24✔
43
      {
44
        // we had some problems - roll back any pending changes
45
        execSql( mConn, "ROLLBACK" );
2✔
46
      }
47
    }
24✔
48

49
    void commitChanges()
23✔
50
    {
51
      assert( mConn );
23✔
52
      execSql( mConn, "COMMIT" );
23✔
53

54
      // reset handler to the database so that the destructor does nothing
55
      mConn = nullptr;
23✔
56
    }
23✔
57

58
  private:
59
    PGconn *mConn = nullptr;
60
};
61

62
/////
63

UNCOV
64
void PostgresDriver::logApplyConflict( const std::string &type, const ChangesetEntry &entry ) const
×
65
{
UNCOV
66
  context()->logger().warn( "CONFLICT: " + type + ":\n" + changesetEntryToJSON( entry ).dump( 2 ) );
×
67
}
×
68

69
PostgresDriver::PostgresDriver( const Context *context )
63✔
70
  : Driver( context )
63✔
71
{
72
}
63✔
73

74
PostgresDriver::~PostgresDriver()
126✔
75
{
76
  close();
63✔
77
}
126✔
78

79
void PostgresDriver::openPrivate( const DriverParametersMap &conn )
63✔
80
{
81
  DriverParametersMap::const_iterator connInfo = conn.find( "conninfo" );
63✔
82
  if ( connInfo == conn.end() )
63✔
UNCOV
83
    throw GeoDiffException( "Missing 'conninfo' parameter" );
×
84
  std::string connInfoStr = connInfo->second;
63✔
85

86
  DriverParametersMap::const_iterator baseSchema = conn.find( "base" );
63✔
87
  if ( baseSchema == conn.end() )
63✔
UNCOV
88
    throw GeoDiffException( "Missing 'base' parameter" );
×
89
  mBaseSchema = baseSchema->second;
63✔
90

91
  DriverParametersMap::const_iterator modifiedSchema = conn.find( "modified" );
63✔
92
  mModifiedSchema = ( modifiedSchema == conn.end() ) ? std::string() : modifiedSchema->second;
63✔
93

94
  if ( mConn )
63✔
UNCOV
95
    throw GeoDiffException( "Connection already opened" );
×
96

97
  PGconn *c = PQconnectdb( connInfoStr.c_str() );
63✔
98

99
  if ( PQstatus( c ) != CONNECTION_OK )
63✔
100
  {
NEW
101
    std::string msg( PQerrorMessage( c ) );
×
NEW
102
    PQfinish( c );
×
NEW
103
    throw GeoDiffException( "Cannot connect to PostgreSQL database: " + msg );
×
UNCOV
104
  }
×
105

106
  mConn = c;
63✔
107

108
  // Make sure we are using enough digits for floating point numbers to make sure that we are
109
  // not loosing any digits when querying data.
110
  // https://www.postgresql.org/docs/12/runtime-config-client.html#GUC-EXTRA-FLOAT-DIGITS
111
  execSql( mConn, "SET extra_float_digits = 2;" );
63✔
112
}
63✔
113

114
void PostgresDriver::close()
63✔
115
{
116
  mBaseSchema.clear();
63✔
117
  mModifiedSchema.clear();
63✔
118
  if ( mConn )
63✔
119
    PQfinish( mConn );
63✔
120
  mConn = nullptr;
63✔
121
}
63✔
122

123
void PostgresDriver::open( const DriverParametersMap &conn )
47✔
124
{
125
  openPrivate( conn );
47✔
126

127
  {
128
    PostgresResult resBase = execSql( mConn, "SELECT 1 FROM pg_namespace WHERE nspname = " + quotedString( mBaseSchema ) );
47✔
129
    if ( resBase.rowCount() == 0 )
47✔
130
    {
UNCOV
131
      std::string baseSchema = mBaseSchema;  // close() will erase mBaseSchema...
×
UNCOV
132
      close();
×
UNCOV
133
      throw GeoDiffException( "The base schema does not exist: " + baseSchema );
×
UNCOV
134
    }
×
135
  }
47✔
136

137
  if ( !mModifiedSchema.empty() )
47✔
138
  {
139
    PostgresResult resBase = execSql( mConn, "SELECT 1 FROM pg_namespace WHERE nspname = " + quotedString( mModifiedSchema ) );
16✔
140
    if ( resBase.rowCount() == 0 )
16✔
141
    {
UNCOV
142
      std::string modifiedSchema = mModifiedSchema;  // close() will erase mModifiedSchema...
×
UNCOV
143
      close();
×
UNCOV
144
      throw GeoDiffException( "The base schema does not exist: " + modifiedSchema );
×
UNCOV
145
    }
×
146
  }
16✔
147
}
47✔
148

149
void PostgresDriver::create( const DriverParametersMap &conn, bool overwrite )
16✔
150
{
151
  openPrivate( conn );
16✔
152

153
  std::string sql;
16✔
154
  if ( overwrite )
16✔
155
    sql += "DROP SCHEMA IF EXISTS " + quotedIdentifier( mBaseSchema ) + " CASCADE; ";
15✔
156
  sql += "CREATE SCHEMA " + quotedIdentifier( mBaseSchema ) + ";";
16✔
157

158
  execSql( mConn, sql );
16✔
159
}
16✔
160

161

162
std::vector<std::string> PostgresDriver::listTables( bool useModified )
69✔
163
{
164
  if ( !mConn )
69✔
165
    throw GeoDiffException( "Not connected to a database" );
×
166
  if ( useModified && mModifiedSchema.empty() )
69✔
UNCOV
167
    throw GeoDiffException( "Should use modified schema, but it was not set" );
×
168

169
  std::string schemaName = useModified ? mModifiedSchema : mBaseSchema;
69✔
170
  std::string sql = "select tablename from pg_tables where schemaname=" + quotedString( schemaName );
69✔
171
  PostgresResult res = execSql( mConn, sql );
69✔
172

173
  std::vector<std::string> tables;
69✔
174
  for ( int i = 0; i < res.rowCount(); ++i )
169✔
175
  {
176
    if ( startsWith( res.value( i, 0 ), "gpkg_" ) )
200✔
UNCOV
177
      continue;
×
178

179
    if ( context()->isTableSkipped( res.value( i, 0 ) ) )
100✔
UNCOV
180
      continue;
×
181

182
    tables.push_back( res.value( i, 0 ) );
100✔
183
  }
184

185
  // make sure tables are in alphabetical order, so that if we compare table names
186
  // from different schemas, they should be matching
187
  std::sort( tables.begin(), tables.end() );
69✔
188

189
  return tables;
138✔
190
}
69✔
191

192
struct GeometryTypeDetails
193
{
194
  // cppcheck-suppress unusedStructMember
195
  const char *flatType;
196
  // cppcheck-suppress unusedStructMember
197
  bool hasZ;
198
  // cppcheck-suppress unusedStructMember
199
  bool hasM;
200
};
201

202
static void extractGeometryTypeDetails( const std::string &geomType, const std::string &coordinateDimension, std::string &flatGeomType, bool &hasZ, bool &hasM )
132✔
203
{
204
  std::map<std::string, GeometryTypeDetails> d =
205
  {
206
    { "POINT",   { "POINT", false, false } },
207
    { "POINTZ",  { "POINT", true,  false } },
208
    { "POINTM",  { "POINT", false, true  } },
209
    { "POINTZM", { "POINT", true,  true  } },
210
    { "LINESTRING",   { "LINESTRING", false, false } },
211
    { "LINESTRINGZ",  { "LINESTRING", true,  false } },
212
    { "LINESTRINGM",  { "LINESTRING", false, true  } },
213
    { "LINESTRINGZM", { "LINESTRING", true,  true  } },
214
    { "POLYGON",   { "POLYGON", false, false } },
215
    { "POLYGONZ",  { "POLYGON", true,  false } },
216
    { "POLYGONM",  { "POLYGON", false, true  } },
217
    { "POLYGONZM", { "POLYGON", true,  true  } },
218

219
    { "MULTIPOINT",   { "MULTIPOINT", false, false } },
220
    { "MULTIPOINTZ",  { "MULTIPOINT", true,  false } },
221
    { "MULTIPOINTM",  { "MULTIPOINT", false, true  } },
222
    { "MULTIPOINTZM", { "MULTIPOINT", true,  true  } },
223
    { "MULTILINESTRING",   { "MULTILINESTRING", false, false } },
224
    { "MULTILINESTRINGZ",  { "MULTILINESTRING", true,  false } },
225
    { "MULTILINESTRINGM",  { "MULTILINESTRING", false, true  } },
226
    { "MULTILINESTRINGZM", { "MULTILINESTRING", true,  true  } },
227
    { "MULTIPOLYGON",   { "MULTIPOLYGON", false, false } },
228
    { "MULTIPOLYGONZ",  { "MULTIPOLYGON", true,  false } },
229
    { "MULTIPOLYGONM",  { "MULTIPOLYGON", false, true  } },
230
    { "MULTIPOLYGONZM", { "MULTIPOLYGON", true,  true  } },
231

232
    { "GEOMETRYCOLLECTION",   { "GEOMETRYCOLLECTION", false, false } },
233
    { "GEOMETRYCOLLECTIONZ",  { "GEOMETRYCOLLECTION", true,  false } },
234
    { "GEOMETRYCOLLECTIONM",  { "GEOMETRYCOLLECTION", false, true  } },
235
    { "GEOMETRYCOLLECTIONZM", { "GEOMETRYCOLLECTION", true,  true  } },
236

237
    // TODO: curve geometries
238
  };
3,960✔
239

240
  /*
241
   *  Special PostGIS coding of xyZ, xyM and xyZM type https://postgis.net/docs/using_postgis_dbmanagement.html#geometry_columns
242
   *  coordinateDimension number bears information about third and fourth dimension.
243
   */
244
  std::string type = geomType;
132✔
245

246
  if ( coordinateDimension == "4" )
132✔
247
  {
248
    type += "ZM";
18✔
249
  }
250
  else if ( ( coordinateDimension == "3" ) && ( type.back() != 'M' ) )
114✔
251
  {
252
    type += "Z";
18✔
253
  }
254

255
  auto it = d.find( type );
132✔
256
  if ( it != d.end() )
132✔
257
  {
258
    flatGeomType = it->second.flatType;
132✔
259
    hasZ = it->second.hasZ;
132✔
260
    hasM = it->second.hasM;
132✔
261
  }
262
  else
UNCOV
263
    throw GeoDiffException( "Unknown geometry type: " + type );
×
264
}
660✔
265

266

267
TableSchema PostgresDriver::tableSchema( const std::string &tableName, bool useModified )
145✔
268
{
269
  if ( !mConn )
145✔
270
    throw GeoDiffException( "Not connected to a database" );
×
271
  if ( useModified && mModifiedSchema.empty() )
145✔
UNCOV
272
    throw GeoDiffException( "Should use modified schema, but it was not set" );
×
273

274
  std::string schemaName = useModified ? mModifiedSchema : mBaseSchema;
145✔
275

276
  // try to figure out details of the geometry columns (if any)
277
  std::string sqlGeomDetails = "SELECT f_geometry_column, type, srid, coord_dimension FROM geometry_columns WHERE f_table_schema = " +
290✔
278
                               quotedString( schemaName ) + " AND f_table_name = " + quotedString( tableName );
435✔
279
  std::map<std::string, std::pair<std::string, std::string>> geomTypes;
145✔
280
  std::map<std::string, int> geomSrids;
145✔
281
  PostgresResult resGeomDetails = execSql( mConn, sqlGeomDetails );
145✔
282
  for ( int i = 0; i < resGeomDetails.rowCount(); ++i )
277✔
283
  {
284
    std::string name = resGeomDetails.value( i, 0 );
132✔
285
    std::string type = resGeomDetails.value( i, 1 );
132✔
286
    std::string srid = resGeomDetails.value( i, 2 );
132✔
287
    std::string dimension = resGeomDetails.value( i, 3 );
132✔
288
    int sridInt = srid.empty() ? -1 : atoi( srid.c_str() );
132✔
289
    geomTypes[name] = { type, dimension };
132✔
290
    geomSrids[name] = sridInt;
132✔
291
  }
132✔
292

293
  std::string sqlColumns =
294
    "SELECT a.attname, pg_catalog.format_type(a.atttypid, a.atttypmod), i.indisprimary, a.attnotnull, "
295
    "    EXISTS ("
296
    "             SELECT FROM pg_attrdef ad"
297
    "             WHERE  ad.adrelid = a.attrelid"
298
    "             AND    ad.adnum   = a.attnum"
299
    "             AND    pg_get_expr(ad.adbin, ad.adrelid)"
300
    "                  = 'nextval('''"
301
    "                 || (pg_get_serial_sequence (a.attrelid::regclass::text, a.attname))::regclass"
302
    "                 || '''::regclass)'"
303
    "       ) AS has_sequence"
304
    " FROM pg_catalog.pg_attribute a"
305
    " LEFT JOIN pg_index i ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey)"
306
    " WHERE"
307
    "   a.attnum > 0"
308
    "   AND NOT a.attisdropped"
309
    "   AND a.attrelid = ("
310
    "      SELECT c.oid"
311
    "        FROM pg_catalog.pg_class c"
312
    "        LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace"
313
    "        WHERE c.relname = " + quotedString( tableName ) +
290✔
314
    "          AND n.nspname = " + quotedString( schemaName ) +
580✔
315
    "   )"
316
    "  ORDER BY a.attnum";
145✔
317

318
  PostgresResult res = execSql( mConn, sqlColumns );
145✔
319

320
  int srsId = -1;
145✔
321
  TableSchema schema;
145✔
322
  schema.name = tableName;
145✔
323
  for ( int i = 0; i < res.rowCount(); ++i )
755✔
324
  {
325
    TableColumnInfo col;
610✔
326
    col.name = res.value( i, 0 );
610✔
327
    col.isPrimaryKey = ( res.value( i, 2 ) == "t" );
610✔
328
    std::string type( res.value( i, 1 ) );
610✔
329

330
    if ( startsWith( type, "geometry" ) )
1,220✔
331
    {
332
      std::string geomTypeName;
132✔
333
      bool hasM = false;
132✔
334
      bool hasZ = false;
132✔
335

336
      if ( geomTypes.find( col.name ) != geomTypes.end() )
132✔
337
      {
338
        extractGeometryTypeDetails( geomTypes[col.name].first, geomTypes[col.name].second, geomTypeName, hasZ, hasM );
132✔
339
        srsId = geomSrids[col.name];
132✔
340
      }
341
      col.setGeometry( geomTypeName, srsId, hasM, hasZ );
132✔
342
    }
132✔
343

344
    col.type = columnType( context(), type, Driver::POSTGRESDRIVERNAME, col.isGeometry );
610✔
345
    col.isNotNull = ( res.value( i, 3 ) == "t" );
610✔
346
    col.isAutoIncrement = ( res.value( i, 4 ) == "t" );
610✔
347

348
    schema.columns.push_back( col );
610✔
349
  }
610✔
350

351
  //
352
  // get CRS details
353
  //
354

355
  if ( srsId != -1 )
145✔
356
  {
357
    PostgresResult resCrs = execSql( mConn,
358
                                     "SELECT auth_name, auth_srid, srtext "
359
                                     "FROM spatial_ref_sys WHERE srid = " + std::to_string( srsId ) );
132✔
360

361
    if ( resCrs.rowCount() == 0 )
132✔
UNCOV
362
      throw GeoDiffException( "Unknown CRS in table " + tableName );
×
363
    schema.crs.srsId = srsId;
132✔
364
    schema.crs.authName = resCrs.value( 0, 0 );
132✔
365
    schema.crs.authCode = atoi( resCrs.value( 0, 1 ).c_str() );
132✔
366
    schema.crs.wkt = resCrs.value( 0, 2 );
132✔
367
  }
132✔
368

369
  return schema;
290✔
370
}
145✔
371

372

373
static std::string allColumnNames( const TableSchema &tbl, const std::string &prefix = "" )
104✔
374
{
375
  std::string columns;
104✔
376
  for ( const TableColumnInfo &c : tbl.columns )
530✔
377
  {
378
    if ( !columns.empty() )
426✔
379
      columns += ", ";
322✔
380

381
    std::string name;
426✔
382
    if ( !prefix.empty() )
426✔
383
      name = prefix + ".";
158✔
384
    name += quotedIdentifier( c.name );
426✔
385

386
    if ( c.isGeometry )
426✔
387
      columns += "ST_AsBinary(" + name + ")";
91✔
388
    else if ( c.type == "timestamp without time zone" )
670✔
389
    {
390
      // by default postgresql would return date/time as a formatted string
391
      // e.g. "2020-07-13 16:17:54" but we want IS0-8601 format "2020-07-13T16:17:54.060110Z"
392
      // Our format needs to capture the full resolution of Postgres timestamps (microseconds)
393
      columns += "to_char(" + name + ",'YYYY-MM-DD\"T\"HH24:MI:SS.US\"Z\"')";
10✔
394
    }
395
    else
396
      columns += name;
325✔
397
  }
426✔
398
  return columns;
104✔
UNCOV
399
}
×
400

401

402
//! Constructs SQL query to get all rows that do not exist in the other table (used for insert and delete)
403
static std::string sqlFindInserted( const std::string &schemaNameBase, const std::string &schemaNameModified, const std::string &tableName, const TableSchema &tbl, bool reverse )
38✔
404
{
405
  std::string exprPk;
38✔
406
  for ( const TableColumnInfo &c : tbl.columns )
196✔
407
  {
408
    if ( c.isPrimaryKey )
158✔
409
    {
410
      if ( !exprPk.empty() )
38✔
UNCOV
411
        exprPk += " AND ";
×
412
      exprPk += quotedIdentifier( schemaNameBase ) + "." + quotedIdentifier( tableName ) + "." + quotedIdentifier( c.name ) + "=" +
76✔
413
                quotedIdentifier( schemaNameModified ) + "." + quotedIdentifier( tableName ) + "." + quotedIdentifier( c.name );
114✔
414
    }
415
  }
416

417
  std::string sql = "SELECT " + allColumnNames( tbl ) + " FROM " +
76✔
418
                    quotedIdentifier( reverse ? schemaNameBase : schemaNameModified ) + "." + quotedIdentifier( tableName ) +
152✔
419
                    " WHERE NOT EXISTS ( SELECT 1 FROM " +
76✔
420
                    quotedIdentifier( reverse ? schemaNameModified : schemaNameBase ) + "." + quotedIdentifier( tableName ) +
152✔
421
                    " WHERE " + exprPk + ")";
38✔
422
  return sql;
76✔
423
}
38✔
424

425

426
static std::string sqlFindModified( const std::string &schemaNameBase, const std::string &schemaNameModified, const std::string &tableName, const TableSchema &tbl )
19✔
427
{
428
  std::string exprPk;
19✔
429
  std::string exprOther;
19✔
430
  for ( const TableColumnInfo &c : tbl.columns )
98✔
431
  {
432
    if ( c.isPrimaryKey )
79✔
433
    {
434
      if ( !exprPk.empty() )
19✔
UNCOV
435
        exprPk += " AND ";
×
436
      exprPk += "b." + quotedIdentifier( c.name ) + "=" +
38✔
437
                "a." + quotedIdentifier( c.name );
57✔
438
    }
439
    else // not a primary key column
440
    {
441
      if ( !exprOther.empty() )
60✔
442
        exprOther += " OR ";
42✔
443

444
      std::string colBase = "b." + quotedIdentifier( c.name );
60✔
445
      std::string colModified = "a." + quotedIdentifier( c.name );
60✔
446

447
      // pg IS DISTINCT FROM operator handles comparison between null and not null values. When comparing values with basic `=` operator,
448
      // comparison 7 = NULL returns null (no rows), not false as one would expect. IS DISTINCT FROM handles this and returns false in such situations.
449
      // When comparing non-null values with IS DISTINCT FROM operator, it works just as `=` does with non-null values.
450
      exprOther += "(" + colBase + " IS DISTINCT FROM " + colModified + ")";
60✔
451
    }
60✔
452
  }
453

454
  std::string sql = "SELECT " + allColumnNames( tbl, "a" ) + ", " + allColumnNames( tbl, "b" ) + " FROM " +
76✔
455
                    quotedIdentifier( schemaNameModified ) + "." + quotedIdentifier( tableName ) + " a, " +
76✔
456
                    quotedIdentifier( schemaNameBase ) + "." + quotedIdentifier( tableName ) + " b" +
76✔
457
                    " WHERE " + exprPk;
19✔
458

459
  if ( !exprOther.empty() )
19✔
460
    sql += " AND (" + exprOther + ")";
18✔
461

462
  return sql;
38✔
463
}
19✔
464

465

466
static bool isColumnInt( const TableColumnInfo &col )
283✔
467
{
468
  return col.type == "integer" || col.type == "smallint" || col.type == "bigint";
1,541✔
469
}
470

471
static bool isColumnDouble( const TableColumnInfo &col )
173✔
472
{
473
  return col.type == "real" || col.type == "double precision" ||
865✔
474
         startsWith( col.type.dbType, "numeric" ) || startsWith( col.type.dbType, "decimal" );
1,162✔
475
}
476

477
static bool isColumnText( const TableColumnInfo &col )
156✔
478
{
479
  return col.type == "text" || startsWith( col.type.dbType, "text(" ) ||
711✔
480
         col.type == "varchar" || startsWith( col.type.dbType, "varchar(" ) ||
561✔
481
         col.type == "character varying" || startsWith( col.type.dbType, "character varying(" ) ||
557✔
482
         col.type == "char" || startsWith( col.type.dbType, "char(" ) || startsWith( col.type.dbType, "character(" ) ||
1,022✔
483
         col.type == "citext";
468✔
484
}
485

486
static bool isColumnGeometry( const TableColumnInfo &col )
67✔
487
{
488
  return col.isGeometry;
67✔
489
}
490

491
static Value resultToValue( const PostgresResult &res, int r, size_t i, const TableColumnInfo &col )
295✔
492
{
493
  Value v;
295✔
494
  if ( res.isNull( r, i ) )
295✔
495
  {
496
    v.setNull();
11✔
497
  }
498
  else
499
  {
500
    std::string valueStr = res.value( r, i );
284✔
501
    if ( col.type == "bool" || col.type == "boolean" )
1,420✔
502
    {
503
      v.setInt( valueStr == "t" );  // PostgreSQL uses 't' for true and 'f' for false
1✔
504
    }
505
    else if ( isColumnInt( col ) )
283✔
506
    {
507
      v.setInt( atol( valueStr.c_str() ) );
110✔
508
    }
509
    else if ( isColumnDouble( col ) )
173✔
510
    {
511
      v.setDouble( atof( valueStr.c_str() ) );
17✔
512
    }
513
    else if ( isColumnText( col ) || col.type == "uuid" )
312✔
514
    {
515
      v.setString( Value::TypeText, valueStr.c_str(), valueStr.size() );
79✔
516
    }
517
    else if ( col.type == "timestamp without time zone" || col.type == "date" )
367✔
518
    {
519
      v.setString( Value::TypeText, valueStr.c_str(), valueStr.size() );
10✔
520
    }
521
    else if ( isColumnGeometry( col ) )
67✔
522
    {
523
      // the value we get should have this format: "\x01234567890abcdef"
524
      if ( valueStr.size() < 2 )
67✔
UNCOV
525
        throw GeoDiffException( "Unexpected geometry value" );
×
526
      if ( valueStr[0] != '\\' || valueStr[1] != 'x' )
67✔
UNCOV
527
        throw GeoDiffException( "Unexpected prefix in geometry value" );
×
528

529
      // 1. convert from hex representation to proper binary stream
530
      std::string binString = hex2bin( valueStr.substr( 2 ) ); // chop \x prefix
67✔
531

532
      // 2. create binary header
533
      std::string binHead = createGpkgHeader( binString, col );
67✔
534

535
      // 3. copy header and body
536
      std::string gpb( binHead.size() + binString.size(), 0 );
67✔
537

538
      memcpy( &gpb[0], binHead.data(), binHead.size() );
67✔
539
      memcpy( &gpb[binHead.size()], binString.data(), binString.size() );
67✔
540

541
      v.setString( Value::TypeBlob, gpb.data(), gpb.size() );
67✔
542
    }
67✔
543
    else
544
    {
545
      // TODO: handling of other types (list, blob, ...)
UNCOV
546
      throw GeoDiffException( "unknown value type: " + col.type.dbType );
×
547
    }
548
  }
284✔
549
  return v;
295✔
UNCOV
550
}
×
551

552
static std::string valueToSql( const Value &v, const TableColumnInfo &col )
335✔
553
{
554
  if ( v.type() == Value::TypeUndefined )
335✔
555
  {
UNCOV
556
    throw GeoDiffException( "valueToSql: got 'undefined' value (malformed changeset?)" );
×
557
  }
558
  else if ( v.type() == Value::TypeNull )
335✔
559
  {
560
    return "NULL";
24✔
561
  }
562
  else if ( v.type() == Value::TypeInt )
323✔
563
  {
564
    if ( col.type == "boolean" )
230✔
565
      return v.getInt() ? "'t'" : "'f'";
6✔
566
    else
567
      return std::to_string( v.getInt() );
112✔
568
  }
569
  else if ( v.type() == Value::TypeDouble )
208✔
570
  {
571
    return to_string_with_max_precision( v.getDouble() );
17✔
572
  }
573
  else if ( v.type() == Value::TypeText || v.type() == Value::TypeBlob )
191✔
574
  {
575
    if ( col.isGeometry )
191✔
576
    {
577
      // handling of geometries - they are encoded with GPKG header
578
      std::string gpkgWkb = v.getString();
76✔
579
      int headerSize = parseGpkgbHeaderSize( gpkgWkb );
76✔
580
      std::string wkb( gpkgWkb.size() - headerSize, 0 );
76✔
581

582
      memcpy( &wkb[0], &gpkgWkb[headerSize], gpkgWkb.size() - headerSize );
76✔
583
      return "ST_GeomFromWKB('\\x" + bin2hex( wkb ) + "', " + std::to_string( col.geomSrsId ) + ")";
76✔
584
    }
76✔
585
    return quotedString( v.getString() );
115✔
586
  }
587
  else
588
  {
UNCOV
589
    throw GeoDiffException( "unexpected value" );
×
590
  }
591
}
592

593

594
static void handleInserted( const std::string &schemaNameBase, const std::string &schemaNameModified, const std::string &tableName, const TableSchema &tbl, bool reverse, PGconn *conn, ChangesetWriter &writer, bool &first )
38✔
595
{
596
  std::string sqlInserted = sqlFindInserted( schemaNameBase, schemaNameModified, tableName, tbl, reverse );
38✔
597
  PostgresResult res = execSql( conn, sqlInserted );
38✔
598

599
  int rows = res.rowCount();
38✔
600
  for ( int r = 0; r < rows; ++r )
51✔
601
  {
602
    if ( first )
13✔
603
    {
604
      ChangesetTable chTable = schemaToChangesetTable( tableName, tbl );
10✔
605
      writer.beginTable( chTable );
10✔
606
      first = false;
10✔
607
    }
10✔
608

609
    ChangesetEntry e;
13✔
610
    e.op = reverse ? ChangesetEntry::OpDelete : ChangesetEntry::OpInsert;
13✔
611

612
    size_t numColumns = tbl.columns.size();
13✔
613
    for ( size_t i = 0; i < numColumns; ++i )
53✔
614
    {
615
      Value v( resultToValue( res, r, i, tbl.columns[i] ) );
40✔
616
      if ( reverse )
40✔
617
        e.oldValues.push_back( v );
4✔
618
      else
619
        e.newValues.push_back( v );
36✔
620
    }
40✔
621

622
    writer.writeEntry( e );
13✔
623
  }
13✔
624
}
38✔
625

626

627
static void handleUpdated( const std::string &schemaNameBase, const std::string &schemaNameModified, const std::string &tableName, const TableSchema &tbl, PGconn *conn, ChangesetWriter &writer, bool &first )
19✔
628
{
629
  std::string sqlModified = sqlFindModified( schemaNameBase, schemaNameModified, tableName, tbl );
19✔
630
  PostgresResult res = execSql( conn, sqlModified );
19✔
631

632
  int rows = res.rowCount();
19✔
633
  for ( int r = 0; r < rows; ++r )
25✔
634
  {
635
    if ( first )
6✔
636
    {
637
      ChangesetTable chTable = schemaToChangesetTable( tableName, tbl );
2✔
638
      writer.beginTable( chTable );
2✔
639
      first = false;
2✔
640
    }
2✔
641

642
    /*
643
    ** Within the old.* record associated with an UPDATE change, all fields
644
    ** associated with table columns that are not PRIMARY KEY columns and are
645
    ** not modified by the UPDATE change are set to "undefined". Other fields
646
    ** are set to the values that made up the row before the UPDATE that the
647
    ** change records took place. Within the new.* record, fields associated
648
    ** with table columns modified by the UPDATE change contain the new
649
    ** values. Fields associated with table columns that are not modified
650
    ** are set to "undefined".
651
    */
652

653
    ChangesetEntry e;
6✔
654
    e.op = ChangesetEntry::OpUpdate;
6✔
655

656
    size_t numColumns = tbl.columns.size();
6✔
657
    for ( size_t i = 0; i < numColumns; ++i )
27✔
658
    {
659
      Value v1( resultToValue( res, r, i + numColumns, tbl.columns[i] ) );
21✔
660
      Value v2( resultToValue( res, r, i, tbl.columns[i] ) );
21✔
661
      bool pkey = tbl.columns[i].isPrimaryKey;
21✔
662
      bool updated = v1 != v2;
21✔
663
      e.oldValues.push_back( ( pkey || updated ) ? v1 : Value() );
34✔
664
      e.newValues.push_back( updated ? v2 : Value() );
21✔
665
    }
21✔
666

667
    writer.writeEntry( e );
6✔
668
  }
6✔
669
}
19✔
670

671

672
void PostgresDriver::createChangeset( ChangesetWriter &writer )
16✔
673
{
674
  if ( !mConn )
16✔
UNCOV
675
    throw GeoDiffException( "Not connected to a database" );
×
676

677
  std::vector<std::string> tablesBase = listTables( false );
16✔
678
  std::vector<std::string> tablesModified = listTables( true );
16✔
679

680
  if ( tablesBase != tablesModified )
16✔
681
  {
682
    throw GeoDiffException( "Table names are not matching between the input databases.\n"
UNCOV
683
                            "Base:     " + concatNames( tablesBase ) + "\n" +
×
UNCOV
684
                            "Modified: " + concatNames( tablesModified ) );
×
685
  }
686

687
  for ( const std::string &tableName : tablesBase )
35✔
688
  {
689
    TableSchema tbl = tableSchema( tableName );
19✔
690
    TableSchema tblNew = tableSchema( tableName, true );
19✔
691

692
    // test that table schema in the modified is the same
693
    if ( tbl != tblNew )
19✔
694
    {
UNCOV
695
      if ( !tbl.compareWithBaseTypes( tblNew ) )
×
UNCOV
696
        throw GeoDiffException( "PostgreSQL Table schemas are not the same for table: " + tableName );
×
697
    }
698

699
    if ( !tbl.hasPrimaryKey() )
19✔
UNCOV
700
      continue;  // ignore tables without primary key - they can't be compared properly
×
701

702
    bool first = true;
19✔
703

704
    handleInserted( mBaseSchema, mModifiedSchema, tableName, tbl, false, mConn, writer, first );  // INSERT
19✔
705
    handleInserted( mBaseSchema, mModifiedSchema, tableName, tbl, true, mConn, writer, first );   // DELETE
19✔
706
    handleUpdated( mBaseSchema, mModifiedSchema, tableName, tbl, mConn, writer, first );          // UPDATE
19✔
707
  }
19✔
708
}
16✔
709

710

711
static std::string sqlForInsert( const std::string &schemaName, const std::string &tableName, const TableSchema &tbl, const std::vector<Value> &values )
76✔
712
{
713
  /*
714
   * For a table defined like this: CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
715
   *
716
   * INSERT INTO x (a, b, c, d) VALUES (?, ?, ?, ?)
717
   */
718

719
  std::string sql;
76✔
720
  sql += "INSERT INTO " + quotedIdentifier( schemaName ) + "." + quotedIdentifier( tableName ) + " (";
76✔
721
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
376✔
722
  {
723
    if ( i > 0 )
300✔
724
      sql += ", ";
224✔
725
    sql += quotedIdentifier( tbl.columns[i].name );
300✔
726
  }
727
  sql += ") VALUES (";
76✔
728
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
376✔
729
  {
730
    if ( i > 0 )
300✔
731
      sql += ", ";
224✔
732
    sql += valueToSql( values[i], tbl.columns[i] );
300✔
733
  }
734
  sql += ")";
76✔
735
  return sql;
76✔
UNCOV
736
}
×
737

738

739
static std::string sqlForUpdate( const std::string &schemaName, const std::string &tableName, const TableSchema &tbl, const std::vector<Value> &oldValues, const std::vector<Value> &newValues )
6✔
740
{
741
  std::string sql;
6✔
742
  sql += "UPDATE " + quotedIdentifier( schemaName ) + "." + quotedIdentifier( tableName ) + " SET ";
6✔
743

744
  bool first = true;
6✔
745
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
26✔
746
  {
747
    if ( newValues[i].type() != Value::TypeUndefined )
20✔
748
    {
749
      if ( !first )
9✔
750
        sql += ", ";
3✔
751
      first = false;
9✔
752
      sql += quotedIdentifier( tbl.columns[i].name ) + " = " + valueToSql( newValues[i], tbl.columns[i] );
9✔
753
    }
754
  }
755
  first = true;
6✔
756
  sql += " WHERE ";
6✔
757
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
26✔
758
  {
759
    if ( oldValues[i].type() != Value::TypeUndefined )
20✔
760
    {
761
      if ( !first )
15✔
762
        sql += " AND ";
9✔
763
      first = false;
15✔
764
      sql += quotedIdentifier( tbl.columns[i].name );
15✔
765
      if ( oldValues[i].type() == Value::TypeNull )
15✔
766
        sql += " IS NULL";
1✔
767
      else
768
        sql += " = " + valueToSql( oldValues[i], tbl.columns[i] );
14✔
769
    }
770
  }
771

772
  return sql;
6✔
UNCOV
773
}
×
774

775

776
static std::string sqlForDelete( const std::string &schemaName, const std::string &tableName, const TableSchema &tbl, const std::vector<Value> &values )
3✔
777
{
778
  std::string sql;
3✔
779
  sql += "DELETE FROM " + quotedIdentifier( schemaName ) + "." + quotedIdentifier( tableName ) + " WHERE ";
3✔
780
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
15✔
781
  {
782
    if ( i > 0 )
12✔
783
      sql += " AND ";
9✔
784
    if ( tbl.columns[i].isPrimaryKey )
12✔
785
      sql += quotedIdentifier( tbl.columns[i].name ) + " = " + valueToSql( values[i], tbl.columns[i] );
3✔
786
    else
787
    {
788
      if ( values[i].type() == Value::TypeNull )
9✔
UNCOV
789
        sql += quotedIdentifier( tbl.columns[i].name ) + " IS NULL";
×
790
      else
791
        sql += quotedIdentifier( tbl.columns[i].name ) + " = " + valueToSql( values[i], tbl.columns[i] ) ;
9✔
792
    }
793
  }
794
  return sql;
3✔
UNCOV
795
}
×
796

797
static bool isIntegrityError( const PostgresResult &res )
1✔
798
{
799
  return res.sqlState().substr( 0, 2 ) == "23";
1✔
800
}
801

802
ChangeApplyResult PostgresDriver::applyChange( PostgresChangeApplyState &state, const ChangesetEntry &entry )
86✔
803
{
804
  std::string tableName = entry.table->name;
86✔
805

806
  // skip any changes to GPKG meta tables
807
  if ( startsWith( tableName, "gpkg_" ) )
172✔
808
    return ChangeApplyResult::Skipped;
1✔
809

810
  // skip table if asked to
811
  if ( context()->isTableSkipped( tableName ) )
85✔
UNCOV
812
    return ChangeApplyResult::Skipped;
×
813

814
  if ( state.tableState.count( tableName ) == 0 )
85✔
815
  {
816
    TableSchema schema = tableSchema( tableName );
44✔
817

818
    if ( schema.columns.size() == 0 )
44✔
UNCOV
819
      throw GeoDiffException( "No such table: " + tableName );
×
820

821
    if ( schema.columns.size() != entry.table->columnCount() )
44✔
UNCOV
822
      throw GeoDiffException( "Wrong number of columns for table: " + tableName );
×
823

824
    for ( size_t i = 0; i < entry.table->columnCount(); ++i )
232✔
825
    {
826
      if ( schema.columns[i].isPrimaryKey != entry.table->primaryKeys[i] )
188✔
UNCOV
827
        throw GeoDiffException( "Mismatch of primary keys in table: " + tableName );
×
828
    }
829

830
    PostgresChangeApplyState::TableState &tbl = state.tableState[tableName];
44✔
831
    tbl.schema = schema;
44✔
832
    // if a table has auto-incrementing pkey (using SEQUENCE object), we may need
833
    // to update the sequence value after doing some inserts (or subsequent INSERTs would fail)
834
    std::string seqName = getSequenceObjectName( tbl.schema, tbl.autoIncrementPkeyIndex );
44✔
835
    if ( tbl.autoIncrementPkeyIndex != -1 )
44✔
836
      tbl.sequenceName = seqName;
44✔
837
  }
44✔
838
  // Create savepoint so we have somewhere to rollback to if the command fails
839
  execSql( mConn, "SAVEPOINT geodiff_apply" );
85✔
840

841
  try
842
  {
843
    PostgresChangeApplyState::TableState &tbl = state.tableState[tableName];
85✔
844
    if ( entry.op == ChangesetEntry::OpInsert )
85✔
845
    {
846
      std::string sql = sqlForInsert( mBaseSchema, tableName, tbl.schema, entry.newValues );
76✔
847
      PostgresResult res = execSql( mConn, sql );
76✔
848
      if ( res.affectedRows() != "1" )
76✔
849
        throw GeoDiffException( "Wrong number of affected rows! Expected 1, got: " + res.affectedRows() );
×
850

851
      if ( tbl.autoIncrementPkeyIndex != -1 )
76✔
852
      {
853
        int64_t pkey = entry.newValues[tbl.autoIncrementPkeyIndex].getInt();
76✔
854
        tbl.autoIncrementMax = std::max( tbl.autoIncrementMax, pkey );
76✔
855
      }
856
    }
76✔
857
    else if ( entry.op == ChangesetEntry::OpUpdate )
9✔
858
    {
859
      std::string sql = sqlForUpdate( mBaseSchema, tableName, tbl.schema, entry.oldValues, entry.newValues );
6✔
860
      PostgresResult res = execSql( mConn, sql );
6✔
861
      if ( res.affectedRows() != "1" )
6✔
862
      {
UNCOV
863
        logApplyConflict( "update_nothing", entry );
×
UNCOV
864
        context()->logger().warn( "Wrong number of affected rows! Expected 1, got: " + res.affectedRows() + "\nSQL: " + sql );
×
865
        return ChangeApplyResult::NoChange;
×
866
      }
867
    }
6✔
868
    else if ( entry.op == ChangesetEntry::OpDelete )
3✔
869
    {
870
      std::string sql = sqlForDelete( mBaseSchema, tableName, tbl.schema, entry.oldValues );
3✔
871
      PostgresResult res = execSql( mConn, sql );
3✔
872
      if ( res.affectedRows() != "1" )
2✔
873
      {
UNCOV
874
        logApplyConflict( "delete_nothing", entry );
×
UNCOV
875
        context()->logger().warn( "Wrong number of affected rows! Expected 1, got: " + res.affectedRows() );
×
UNCOV
876
        return ChangeApplyResult::NoChange;
×
877
      }
878
    }
3✔
879
    else
UNCOV
880
      throw GeoDiffException( "Unexpected operation" );
×
881
  }
882
  catch ( GeoDiffPostgresException &ex )
1✔
883
  {
884
    if ( isIntegrityError( ex.result() ) )
1✔
885
    {
UNCOV
886
      execSql( mConn, "ROLLBACK TO SAVEPOINT geodiff_apply" );
×
UNCOV
887
      return ChangeApplyResult::ConstraintConflict;
×
888
    }
889
    throw;
1✔
890
  }
1✔
891

892
  execSql( mConn, "RELEASE SAVEPOINT geodiff_apply" );
84✔
893
  return ChangeApplyResult::Applied;
84✔
894
}
86✔
895

896

897
void PostgresDriver::applyChangeset( ChangesetReader &reader )
24✔
898
{
899
  if ( !mConn )
24✔
UNCOV
900
    throw GeoDiffException( "Not connected to a database" );
×
901

902
  // start a transaction, so that all changes get committed at once (or nothing get committed)
903
  PostgresTransaction transaction( mConn );
24✔
904
  execSql( mConn, "SET CONSTRAINTS ALL DEFERRED" );
24✔
905

906
  // See sqlitedriver.cpp for why and how we're trying to apply changes
907
  // multiple times
908
  int unrecoverableConflictCount = 0;
24✔
909
  std::vector<ChangesetEntry> conflictingEntries;
24✔
910
  ChangesetEntry entry;
24✔
911
  PostgresChangeApplyState state;
24✔
912
  std::unordered_map<std::string, std::unique_ptr<ChangesetTable>> tableCopies;
24✔
913
  while ( reader.nextEntry( entry ) )
109✔
914
  {
915
    ChangeApplyResult res = applyChange( state, entry );
86✔
916
    switch ( res )
85✔
917
    {
918
      case ChangeApplyResult::Applied:
85✔
919
      case ChangeApplyResult::Skipped:
920
        break;
85✔
UNCOV
921
      case ChangeApplyResult::ConstraintConflict:
×
UNCOV
922
        if ( tableCopies.count( entry.table->name ) == 0 )
×
923
          // cppcheck-suppress stlFindInsert
UNCOV
924
          tableCopies[entry.table->name] = std::unique_ptr<ChangesetTable>( new ChangesetTable( *entry.table ) );
×
UNCOV
925
        entry.table = tableCopies[entry.table->name].get();
×
UNCOV
926
        conflictingEntries.push_back( entry );
×
UNCOV
927
        break;
×
UNCOV
928
      case ChangeApplyResult::NoChange:
×
UNCOV
929
        unrecoverableConflictCount++;
×
930
        break;
×
931
    }
932
  }
933

934
  std::vector<ChangesetEntry> newConflictingEntries;
46✔
935
  while ( conflictingEntries.size() > 0 )
23✔
936
  {
UNCOV
937
    for ( const ChangesetEntry &centry : conflictingEntries )
×
938
    {
UNCOV
939
      ChangeApplyResult res = applyChange( state, centry );
×
UNCOV
940
      switch ( res )
×
941
      {
UNCOV
942
        case ChangeApplyResult::Applied:
×
943
        case ChangeApplyResult::Skipped:
UNCOV
944
          break;
×
UNCOV
945
        case ChangeApplyResult::ConstraintConflict:
×
UNCOV
946
          newConflictingEntries.push_back( centry );
×
UNCOV
947
          break;
×
UNCOV
948
        case ChangeApplyResult::NoChange:
×
949
          unrecoverableConflictCount++;
×
UNCOV
950
          break;
×
951
      }
952
    }
953

UNCOV
954
    if ( newConflictingEntries.size() == conflictingEntries.size() )
×
955
    {
UNCOV
956
      for ( const ChangesetEntry &centry : conflictingEntries )
×
UNCOV
957
        logApplyConflict( "unresolvable_conflict", centry );
×
UNCOV
958
      throw GeoDiffException( "Could not resolve dependencies in constraint conflicts." );
×
959
    }
UNCOV
960
    conflictingEntries = newConflictingEntries;
×
UNCOV
961
    newConflictingEntries.clear();
×
962
  }
963

964
  // at the end, update any SEQUENCE objects if needed
965
  for ( const auto &it : state.tableState )
66✔
966
    if ( it.second.autoIncrementMax )
43✔
967
      updateSequenceObject( it.second.sequenceName, it.second.autoIncrementMax );
40✔
968

969
  if ( !unrecoverableConflictCount )
23✔
970
  {
971
    transaction.commitChanges();
23✔
972
  }
973
  else
974
  {
UNCOV
975
    throw GeoDiffException( "Conflicts encountered while applying changes! Total " + std::to_string( unrecoverableConflictCount ) );
×
976
  }
977
}
28✔
978

979
std::string PostgresDriver::getSequenceObjectName( const TableSchema &tbl, int &autoIncrementPkeyIndex )
44✔
980
{
981
  std::string colName;
44✔
982
  autoIncrementPkeyIndex = -1;
44✔
983
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
44✔
984
  {
985
    if ( tbl.columns[i].isPrimaryKey && tbl.columns[i].isAutoIncrement )
44✔
986
    {
987
      autoIncrementPkeyIndex = i;
44✔
988
      colName = tbl.columns[i].name;
44✔
989
      break;
44✔
990
    }
991
  }
992

993
  if ( autoIncrementPkeyIndex == -1 )
44✔
UNCOV
994
    return "";
×
995

996
  std::string tableNameString = quotedIdentifier( mBaseSchema ) + "." + quotedIdentifier( tbl.name );
44✔
997
  std::string sql = "select pg_get_serial_sequence(" + quotedString( tableNameString ) + ", " + quotedString( colName ) + ")";
44✔
998
  PostgresResult resBase = execSql( mConn, sql );
44✔
999
  if ( resBase.rowCount() != 1 )
44✔
1000
    throw GeoDiffException( "Unable to find sequence object for auto-incrementing pkey for table " + tbl.name );
×
1001

1002
  return resBase.value( 0, 0 );
44✔
1003
}
44✔
1004

1005
void PostgresDriver::updateSequenceObject( const std::string &seqName, int64_t maxValue )
40✔
1006
{
1007
  PostgresResult resCurrVal = execSql( mConn, "SELECT last_value FROM " + seqName );
40✔
1008
  std::string currValueStr = resCurrVal.value( 0, 0 );
40✔
1009
  int currValue = std::stoi( currValueStr );
40✔
1010

1011
  if ( currValue < maxValue )
40✔
1012
  {
1013
    context()->logger().info( "Updating sequence " + seqName + " from " + std::to_string( currValue ) + " to " + std::to_string( maxValue ) );
20✔
1014

1015
    std::string sql = "SELECT setval(" + quotedString( seqName ) + ", " + std::to_string( maxValue ) + ")";
20✔
1016
    execSql( mConn, sql );
20✔
1017
    // the SQL just returns the new value we set
1018
  }
20✔
1019
}
40✔
1020

1021
void PostgresDriver::createTables( const std::vector<TableSchema> &tables )
16✔
1022
{
1023
  for ( const TableSchema &tbl : tables )
53✔
1024
  {
1025
    if ( startsWith( tbl.name, "gpkg_" ) )
74✔
UNCOV
1026
      continue;   // skip any changes to GPKG meta tables
×
1027

1028
    std::string sql, pkeyCols, columns;
37✔
1029
    for ( const TableColumnInfo &c : tbl.columns )
195✔
1030
    {
1031
      if ( !columns.empty() )
158✔
1032
        columns += ", ";
121✔
1033

1034
      std::string type = c.type.dbType;
158✔
1035
      if ( c.isAutoIncrement )
158✔
1036
        type = "SERIAL";   // there is also "smallserial", "bigserial" ...
37✔
1037
      columns += quotedIdentifier( c.name ) + " " + type;
158✔
1038

1039
      if ( c.isNotNull )
158✔
1040
        columns += " NOT NULL";
36✔
1041

1042
      if ( c.isPrimaryKey )
158✔
1043
      {
1044
        if ( !pkeyCols.empty() )
37✔
UNCOV
1045
          pkeyCols += ", ";
×
1046
        pkeyCols += quotedIdentifier( c.name );
37✔
1047
      }
1048
    }
158✔
1049

1050
    sql = "CREATE TABLE " + quotedIdentifier( mBaseSchema ) + "." + quotedIdentifier( tbl.name ) + " (";
37✔
1051
    sql += columns;
37✔
1052
    sql += ", PRIMARY KEY (" + pkeyCols + ")";
37✔
1053
    sql += ");";
37✔
1054

1055
    PostgresResult res = execSql( mConn, sql );
37✔
1056
    if ( res.status() != PGRES_COMMAND_OK )
37✔
UNCOV
1057
      throw GeoDiffException( "Failure creating table: " + res.statusErrorMessage() );
×
1058
  }
37✔
1059
}
16✔
1060

1061

1062
void PostgresDriver::dumpData( ChangesetWriter &writer, bool useModified )
15✔
1063
{
1064
  if ( !mConn )
15✔
UNCOV
1065
    throw GeoDiffException( "Not connected to a database" );
×
1066

1067
  std::vector<std::string> tables = listTables();
15✔
1068
  for ( const std::string &tableName : tables )
43✔
1069
  {
1070
    TableSchema tbl = tableSchema( tableName, useModified );
28✔
1071
    if ( !tbl.hasPrimaryKey() )
28✔
UNCOV
1072
      continue;  // ignore tables without primary key - they can't be compared properly
×
1073

1074
    std::string sql = "SELECT " + allColumnNames( tbl ) + " FROM " +
56✔
1075
                      quotedIdentifier( useModified ? mModifiedSchema : mBaseSchema ) + "." + quotedIdentifier( tableName );
84✔
1076

1077
    PostgresResult res = execSql( mConn, sql );
28✔
1078
    int rows = res.rowCount();
28✔
1079
    for ( int r = 0; r < rows; ++r )
83✔
1080
    {
1081
      if ( r == 0 )
55✔
1082
      {
1083
        writer.beginTable( schemaToChangesetTable( tableName, tbl ) );
28✔
1084
      }
1085

1086
      ChangesetEntry e;
55✔
1087
      e.op = ChangesetEntry::OpInsert;
55✔
1088
      size_t numColumns = tbl.columns.size();
55✔
1089
      for ( size_t i = 0; i < numColumns; ++i )
268✔
1090
      {
1091
        e.newValues.push_back( Value( resultToValue( res, r, i, tbl.columns[i] ) ) );
213✔
1092
      }
1093
      writer.writeEntry( e );
55✔
1094
    }
55✔
1095
  }
28✔
1096
}
15✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc