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

MerginMaps / geodiff / 20364107471

19 Dec 2025 08:18AM UTC coverage: 88.063% (+0.04%) from 88.019%
20364107471

push

github

wonder-sk
Improve Postgres constraint error handling

3 of 9 new or added lines in 3 files covered. (33.33%)

148 existing lines in 5 files now uncovered.

3637 of 4130 relevant lines covered (88.06%)

573.59 hits per line

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

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

6
#include "sqlitedriver.h"
7

8
#include "changesetreader.h"
9
#include "changesetwriter.h"
10
#include "changesetutils.h"
11
#include "geodiffcontext.hpp"
12
#include "geodifflogger.hpp"
13
#include "geodiffutils.hpp"
14
#include "sqliteutils.h"
15

16
#include <memory.h>
17
#include <sqlite3.h>
18

19

20
void SqliteDriver::logApplyConflict( const std::string &type, const ChangesetEntry &entry, bool isDbErr ) const
5✔
21
{
22
  std::string msg = "CONFLICT: " + type;
5✔
23
  if ( isDbErr )
5✔
UNCOV
24
    msg += " (" + std::string( sqlite3_errmsg( mDb->get() ) ) + ")";
×
25
  msg += ":\n" + changesetEntryToJSON( entry ).dump( 2 );
5✔
26
  context()->logger().warn( msg );
5✔
27
}
5✔
28

29
/**
30
 * Wrapper around SQLite database wide mutex.
31
 */
32
class Sqlite3DbMutexLocker
33
{
34
  public:
35
    explicit Sqlite3DbMutexLocker( std::shared_ptr<Sqlite3Db> db )
99✔
36
      : mDb( db )
99✔
37
    {
38
      sqlite3_mutex_enter( sqlite3_db_mutex( mDb.get()->get() ) );
99✔
39
    }
99✔
40
    ~Sqlite3DbMutexLocker()
99✔
41
    {
42
      sqlite3_mutex_leave( sqlite3_db_mutex( mDb.get()->get() ) );
99✔
43
    }
99✔
44

45
  private:
46
    std::shared_ptr<Sqlite3Db> mDb;
47
};
48

49
/**
50
 * Wrapper around SQLite Savepoint Transactions.
51
 *
52
 * Constructor start a trasaction, it needs to be confirmed by a call to commitChanges() when
53
 * changes are ready to be written. If commitChanges() is not called, changes since the constructor
54
 * will be rolled back (so that on exception everything gets cleaned up properly).
55
 */
56
class Sqlite3SavepointTransaction
57
{
58
  public:
59
    explicit Sqlite3SavepointTransaction( const Context *context, std::shared_ptr<Sqlite3Db> db )
99✔
60
      : mDb( db ), mContext( context )
99✔
61
    {
62
      if ( sqlite3_exec( mDb.get()->get(), "SAVEPOINT changeset_apply", 0, 0, 0 ) != SQLITE_OK )
99✔
63
      {
UNCOV
64
        throwSqliteError( mDb.get()->get(), "Unable to start savepoint transaction" );
×
65
      }
66
    }
99✔
67

68
    ~Sqlite3SavepointTransaction()
99✔
69
    {
70
      if ( mDb )
99✔
71
      {
72
        // we had some problems - roll back any pending changes
73
        if ( sqlite3_exec( mDb.get()->get(), "ROLLBACK TO changeset_apply", 0, 0, 0 ) != SQLITE_OK )
8✔
74
        {
UNCOV
75
          logSqliteError( mContext, mDb, "Unable to rollback savepoint transaction" );
×
76
        }
77
        if ( sqlite3_exec( mDb.get()->get(), "RELEASE changeset_apply", 0, 0, 0 ) != SQLITE_OK )
8✔
78
        {
UNCOV
79
          logSqliteError( mContext, mDb, "Unable to release savepoint" );
×
80
        }
81
      }
82
    }
99✔
83

84
    void commitChanges()
93✔
85
    {
86
      assert( mDb );
93✔
87
      // there were no errors - release the savepoint and our changes get saved
88
      if ( sqlite3_exec( mDb.get()->get(), "RELEASE changeset_apply", 0, 0, 0 ) != SQLITE_OK )
93✔
89
      {
90
        throwSqliteError( mDb.get()->get(), "Failed to release savepoint" );
4✔
91
      }
92
      // reset handler to the database so that the destructor does nothing
93
      mDb.reset();
91✔
94
    }
91✔
95

96
  private:
97
    std::shared_ptr<Sqlite3Db> mDb;
98
    const Context *mContext;
99
};
100

101

102
///////
103

104

105
SqliteDriver::SqliteDriver( const Context *context )
424✔
106
  : Driver( context )
424✔
107
{
108
}
424✔
109

110
void SqliteDriver::open( const DriverParametersMap &conn )
409✔
111
{
112
  DriverParametersMap::const_iterator connBaseIt = conn.find( "base" );
409✔
113
  if ( connBaseIt == conn.end() )
409✔
114
    throw GeoDiffException( "Missing 'base' file" );
3✔
115

116
  DriverParametersMap::const_iterator connModifiedIt = conn.find( "modified" );
408✔
117
  mHasModified = connModifiedIt != conn.end();
408✔
118

119
  std::string base = connBaseIt->second;
408✔
120
  if ( !fileexists( base ) )
408✔
121
  {
122
    throw GeoDiffException( "Missing 'base' file when opening sqlite driver: " + base );
6✔
123
  }
124

125
  mDb = std::make_shared<Sqlite3Db>();
402✔
126
  if ( mHasModified )
402✔
127
  {
128
    std::string modified = connModifiedIt->second;
265✔
129

130
    if ( !fileexists( modified ) )
265✔
131
    {
132
      throw GeoDiffException( "Missing 'modified' file when opening sqlite driver: " + modified );
2✔
133
    }
134

135
    mDb->open( modified );
263✔
136

137
    Buffer sqlBuf;
263✔
138
    sqlBuf.printf( "ATTACH '%q' AS aux", base.c_str() );
263✔
139
    mDb->exec( sqlBuf );
263✔
140
  }
266✔
141
  else
142
  {
143
    mDb->open( base );
137✔
144
  }
145

146
  // GeoPackage triggers require few functions like ST_IsEmpty() to be registered
147
  // in order to be able to apply changesets
148
  if ( isGeoPackage( context(), mDb ) )
399✔
149
  {
150
    register_gpkg_extensions( mDb );
336✔
151
  }
152

153
  // Enable foreign key constraints (if the database has any)
154
  Buffer sqlBuf;
399✔
155
  sqlBuf.printf( "PRAGMA foreign_keys = 1" );
399✔
156
  mDb->exec( sqlBuf );
399✔
157
}
408✔
158

159
void SqliteDriver::create( const DriverParametersMap &conn, bool overwrite )
14✔
160
{
161
  DriverParametersMap::const_iterator connBaseIt = conn.find( "base" );
14✔
162
  if ( connBaseIt == conn.end() )
14✔
UNCOV
163
    throw GeoDiffException( "Missing 'base' file" );
×
164

165
  std::string base = connBaseIt->second;
14✔
166

167
  if ( overwrite )
14✔
168
  {
169
    fileremove( base );  // remove if the file exists already
14✔
170
  }
171

172
  mDb = std::make_shared<Sqlite3Db>();
14✔
173
  mDb->create( base );
14✔
174

175
  // register geopackage related functions in the newly created sqlite database
176
  register_gpkg_extensions( mDb );
14✔
177
}
14✔
178

179
std::string SqliteDriver::databaseName( bool useModified )
1,517✔
180
{
181
  if ( mHasModified )
1,517✔
182
  {
183
    return useModified ? "main" : "aux";
2,544✔
184
  }
185
  else
186
  {
187
    if ( useModified )
245✔
UNCOV
188
      throw GeoDiffException( "'modified' table not open" );
×
189
    return "main";
490✔
190
  }
191
}
192

193
std::vector<std::string> SqliteDriver::listTables( bool useModified )
555✔
194
{
195
  std::string dbName = databaseName( useModified );
555✔
196
  std::vector<std::string> tableNames;
555✔
197
  std::string all_tables_sql = "SELECT name FROM " + dbName + ".sqlite_master\n"
1,110✔
198
                               " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
199
                               " ORDER BY name";
555✔
200
  Sqlite3Stmt statement;
555✔
201
  statement.prepare( mDb, "%s", all_tables_sql.c_str() );
555✔
202
  int rc;
203
  while ( SQLITE_ROW == ( rc = sqlite3_step( statement.get() ) ) )
6,999✔
204
  {
205
    const char *name = reinterpret_cast<const char *>( sqlite3_column_text( statement.get(), 0 ) );
6,444✔
206
    if ( !name )
6,444✔
207
      continue;
5,618✔
208

209
    std::string tableName( name );
12,888✔
210
    /* typically geopackage from ogr would have these (table name is simple)
211
    gpkg_contents
212
    gpkg_extensions
213
    gpkg_geometry_columns
214
    gpkg_ogr_contents
215
    gpkg_spatial_ref_sys
216
    gpkg_tile_matrix
217
    gpkg_tile_matrix_set
218
    rtree_simple_geometry_node
219
    rtree_simple_geometry_parent
220
    rtree_simple_geometry_rowid
221
    simple (or any other name(s) of layers)
222
    sqlite_sequence
223
    */
224

225
    // table handled by triggers trigger_*_feature_count_*
226
    if ( startsWith( tableName, "gpkg_" ) )
12,888✔
227
      continue;
3,338✔
228
    // table handled by triggers rtree_*_geometry_*
229
    if ( startsWith( tableName, "rtree_" ) )
6,212✔
230
      continue;
1,794✔
231
    // internal table for AUTOINCREMENT
232
    if ( tableName == "sqlite_sequence" )
1,312✔
233
      continue;
467✔
234

235
    if ( context()->isTableSkipped( tableName ) )
845✔
236
      continue;
19✔
237

238
    tableNames.push_back( tableName );
826✔
239
  }
6,444✔
240
  if ( rc != SQLITE_DONE )
555✔
241
  {
UNCOV
242
    logSqliteError( context(), mDb, "Failed to list SQLite tables" );
×
243
  }
244

245
  // result is ordered by name
246
  return tableNames;
1,110✔
247
}
555✔
248

249
bool tableExists( std::shared_ptr<Sqlite3Db> db, const std::string &tableName, const std::string &dbName )
1,894✔
250
{
251
  Sqlite3Stmt stmtHasGeomColumnsInfo;
1,894✔
252
  stmtHasGeomColumnsInfo.prepare( db, "SELECT name FROM \"%w\".sqlite_master WHERE type='table' "
1,894✔
253
                                  "AND name='%q'", dbName.c_str(), tableName.c_str() );
254
  return sqlite3_step( stmtHasGeomColumnsInfo.get() ) == SQLITE_ROW;
3,788✔
255
}
1,894✔
256

257
TableSchema SqliteDriver::tableSchema( const std::string &tableName,
948✔
258
                                       bool useModified )
259
{
260
  std::string dbName = databaseName( useModified );
948✔
261

262
  if ( !tableExists( mDb, tableName, dbName ) )
948✔
263
    throw GeoDiffException( "Table does not exist: " + tableName );
2✔
264

265
  TableSchema tbl;
946✔
266
  tbl.name = tableName;
946✔
267
  std::map<std::string, std::string> columnTypes;
946✔
268

269
  Sqlite3Stmt statement;
946✔
270
  statement.prepare( mDb, "PRAGMA '%q'.table_info('%q')", dbName.c_str(), tableName.c_str() );
946✔
271
  int rc;
272
  while ( SQLITE_ROW == ( rc = sqlite3_step( statement.get() ) ) )
4,414✔
273
  {
274
    const unsigned char *zName = sqlite3_column_text( statement.get(), 1 );
3,468✔
275
    if ( zName == nullptr )
3,468✔
UNCOV
276
      throw GeoDiffException( "NULL column name in table schema: " + tableName );
×
277

278
    TableColumnInfo columnInfo;
3,468✔
279
    columnInfo.name = reinterpret_cast<const char *>( zName );
3,468✔
280
    columnInfo.isNotNull = sqlite3_column_int( statement.get(), 3 );
3,468✔
281
    columnInfo.isPrimaryKey = sqlite3_column_int( statement.get(), 5 );
3,468✔
282
    columnTypes[columnInfo.name] = reinterpret_cast<const char *>( sqlite3_column_text( statement.get(), 2 ) );
3,468✔
283

284
    tbl.columns.push_back( columnInfo );
3,468✔
285
  }
3,468✔
286
  if ( rc != SQLITE_DONE )
946✔
287
  {
UNCOV
288
    logSqliteError( context(), mDb, "Failed to get list columns for table " + tableName );
×
289
  }
290

291
  // check if the geometry columns table is present (it may not be if this is a "pure" sqlite file)
292
  if ( tableExists( mDb, "gpkg_geometry_columns", dbName ) )
1,892✔
293
  {
294
    //
295
    // get geometry column details (geometry type, whether it has Z/M values, CRS id)
296
    //
297

298
    int srsId = -1;
758✔
299
    Sqlite3Stmt stmtGeomCol;
758✔
300
    stmtGeomCol.prepare( mDb, "SELECT * FROM \"%w\".gpkg_geometry_columns WHERE table_name = '%q'", dbName.c_str(), tableName.c_str() );
758✔
301
    while ( SQLITE_ROW == ( rc = sqlite3_step( stmtGeomCol.get() ) ) )
1,440✔
302
    {
303
      const unsigned char *chrColumnName = sqlite3_column_text( stmtGeomCol.get(), 1 );
682✔
304
      const unsigned char *chrTypeName = sqlite3_column_text( stmtGeomCol.get(), 2 );
682✔
305
      if ( chrColumnName == nullptr )
682✔
UNCOV
306
        throw GeoDiffException( "NULL column name in gpkg_geometry_columns: " + tableName );
×
307
      if ( chrTypeName == nullptr )
682✔
UNCOV
308
        throw GeoDiffException( "NULL type name in gpkg_geometry_columns: " + tableName );
×
309

310
      std::string geomColName = reinterpret_cast<const char *>( chrColumnName );
1,364✔
311
      std::string geomTypeName = reinterpret_cast<const char *>( chrTypeName );
682✔
312
      srsId = sqlite3_column_int( stmtGeomCol.get(), 3 );
682✔
313
      bool hasZ = sqlite3_column_int( stmtGeomCol.get(), 4 );
682✔
314
      bool hasM = sqlite3_column_int( stmtGeomCol.get(), 5 );
682✔
315

316
      size_t i = tbl.columnFromName( geomColName );
682✔
317
      if ( i == SIZE_MAX )
682✔
UNCOV
318
        throw GeoDiffException( "Inconsistent entry in gpkg_geometry_columns - geometry column not found: " + geomColName );
×
319

320
      TableColumnInfo &col = tbl.columns[i];
682✔
321
      col.setGeometry( geomTypeName, srsId, hasM, hasZ );
682✔
322
    }
682✔
323
    if ( rc != SQLITE_DONE )
758✔
324
    {
UNCOV
325
      logSqliteError( context(), mDb, "Failed to get geometry column info for table " + tableName );
×
326
    }
327

328
    //
329
    // get CRS information
330
    //
331

332
    if ( srsId != -1 )
758✔
333
    {
334
      Sqlite3Stmt stmtCrs;
682✔
335
      stmtCrs.prepare( mDb, "SELECT * FROM \"%w\".gpkg_spatial_ref_sys WHERE srs_id = %d", dbName.c_str(), srsId );
682✔
336
      if ( SQLITE_ROW != sqlite3_step( stmtCrs.get() ) )
682✔
337
      {
UNCOV
338
        throwSqliteError( mDb->get(), "Unable to find entry in gpkg_spatial_ref_sys for srs_id = " + std::to_string( srsId ) );
×
339
      }
340

341
      const unsigned char *chrAuthName = sqlite3_column_text( stmtCrs.get(), 2 );
682✔
342
      const unsigned char *chrWkt = sqlite3_column_text( stmtCrs.get(), 4 );
682✔
343
      if ( chrAuthName == nullptr )
682✔
UNCOV
344
        throw GeoDiffException( "NULL auth name in gpkg_spatial_ref_sys: " + tableName );
×
345
      if ( chrWkt == nullptr )
682✔
UNCOV
346
        throw GeoDiffException( "NULL definition in gpkg_spatial_ref_sys: " + tableName );
×
347

348
      tbl.crs.srsId = srsId;
682✔
349
      tbl.crs.authName = reinterpret_cast<const char *>( chrAuthName );
682✔
350
      tbl.crs.authCode = sqlite3_column_int( stmtCrs.get(), 3 );
682✔
351
      tbl.crs.wkt = reinterpret_cast<const char *>( chrWkt );
682✔
352
    }
682✔
353
  }
758✔
354

355
  // update column types
356
  for ( auto const &it : columnTypes )
4,414✔
357
  {
358
    size_t i = tbl.columnFromName( it.first );
3,468✔
359
    TableColumnInfo &col = tbl.columns[i];
3,468✔
360
    tbl.columns[i].type = columnType( context(), it.second, Driver::SQLITEDRIVERNAME, col.isGeometry );
3,468✔
361

362
    if ( col.isPrimaryKey && ( lowercaseString( col.type.dbType ) == "integer" ) )
3,468✔
363
    {
364
      // sqlite uses auto-increment automatically for INTEGER PRIMARY KEY - https://sqlite.org/autoinc.html
365
      col.isAutoIncrement = true;
939✔
366
    }
367
  }
368

369
  return tbl;
1,892✔
370
}
948✔
371

372
/**
373
 * printf() with sqlite extensions - see https://www.sqlite.org/printf.html
374
 * for extra format options like %q or %Q
375
 */
376
static std::string sqlitePrintf( const char *zFormat, ... )
5,478✔
377
{
378
  va_list ap;
379
  va_start( ap, zFormat );
5,478✔
380
  char *zSql = sqlite3_vmprintf( zFormat, ap );
5,478✔
381
  va_end( ap );
5,478✔
382

383
  if ( zSql == nullptr )
5,478✔
384
  {
UNCOV
385
    throw GeoDiffException( "out of memory" );
×
386
  }
387
  std::string res = reinterpret_cast<const char *>( zSql );
5,478✔
388
  sqlite3_free( zSql );
5,478✔
389
  return res;
10,956✔
UNCOV
390
}
×
391

392
//! Constructs SQL query to get all rows that do not exist in the other table (used for insert and delete)
393
static std::string sqlFindInserted( const std::string &tableName, const TableSchema &tbl, bool reverse )
734✔
394
{
395
  std::string exprPk;
734✔
396
  for ( const TableColumnInfo &c : tbl.columns )
3,404✔
397
  {
398
    if ( c.isPrimaryKey )
2,670✔
399
    {
400
      if ( !exprPk.empty() )
742✔
401
        exprPk += " AND ";
8✔
402
      exprPk += sqlitePrintf( "\"%w\".\"%w\".\"%w\"=\"%w\".\"%w\".\"%w\"",
1,484✔
403
                              "main", tableName.c_str(), c.name.c_str(), "aux", tableName.c_str(), c.name.c_str() );
742✔
404
    }
405
  }
406

407
  std::string sql = sqlitePrintf( "SELECT * FROM \"%w\".\"%w\" WHERE NOT EXISTS ( SELECT 1 FROM \"%w\".\"%w\" WHERE %s)",
408
                                  reverse ? "aux" : "main", tableName.c_str(),
409
                                  reverse ? "main" : "aux", tableName.c_str(), exprPk.c_str() );
734✔
410
  return sql;
1,468✔
411
}
734✔
412

413
//! Constructs SQL query to get all modified rows for a single table
414
static std::string sqlFindModified( const std::string &tableName, const TableSchema &tbl )
367✔
415
{
416
  std::string exprPk;
367✔
417
  std::string exprOther;
367✔
418
  for ( const TableColumnInfo &c : tbl.columns )
1,702✔
419
  {
420
    if ( c.isPrimaryKey )
1,335✔
421
    {
422
      if ( !exprPk.empty() )
371✔
423
        exprPk += " AND ";
4✔
424
      exprPk += sqlitePrintf( "\"%w\".\"%w\".\"%w\"=\"%w\".\"%w\".\"%w\"",
742✔
425
                              "main", tableName.c_str(), c.name.c_str(), "aux", tableName.c_str(), c.name.c_str() );
371✔
426
    }
427
    else // not a primary key column
428
    {
429
      if ( !exprOther.empty() )
964✔
430
        exprOther += " OR ";
598✔
431

432
      exprOther += sqlitePrintf( "\"%w\".\"%w\".\"%w\" IS NOT \"%w\".\"%w\".\"%w\"",
1,928✔
433
                                 "main", tableName.c_str(), c.name.c_str(), "aux", tableName.c_str(), c.name.c_str() );
964✔
434
    }
435
  }
436
  std::string sql;
367✔
437

438
  if ( exprOther.empty() )
367✔
439
  {
440
    sql = sqlitePrintf( "SELECT * FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s",
2✔
441
                        "main", tableName.c_str(), "aux", tableName.c_str(), exprPk.c_str() );
1✔
442
  }
443
  else
444
  {
445
    sql = sqlitePrintf( "SELECT * FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%s)",
732✔
446
                        "main", tableName.c_str(), "aux", tableName.c_str(), exprPk.c_str(), exprOther.c_str() );
366✔
447
  }
448

449
  return sql;
734✔
450
}
367✔
451

452

453
static Value changesetValue( sqlite3_value *v )
1,491✔
454
{
455
  Value x;
1,491✔
456
  int type = sqlite3_value_type( v );
1,491✔
457
  if ( type == SQLITE_NULL )
1,491✔
458
    x.setNull();
113✔
459
  else if ( type == SQLITE_INTEGER )
1,378✔
460
    x.setInt( sqlite3_value_int64( v ) );
678✔
461
  else if ( type == SQLITE_FLOAT )
700✔
462
    x.setDouble( sqlite3_value_double( v ) );
15✔
463
  else if ( type == SQLITE_TEXT )
685✔
464
    x.setString( Value::TypeText, reinterpret_cast<const char *>( sqlite3_value_text( v ) ), sqlite3_value_bytes( v ) );
424✔
465
  else if ( type == SQLITE_BLOB )
261✔
466
    x.setString( Value::TypeBlob, reinterpret_cast<const char *>( sqlite3_value_blob( v ) ), sqlite3_value_bytes( v ) );
261✔
467
  else
UNCOV
468
    throw GeoDiffException( "Unexpected value type" );
×
469

470
  return x;
1,491✔
UNCOV
471
}
×
472

473
static void handleInserted( const Context *context, const std::string &tableName, const TableSchema &tbl, bool reverse, std::shared_ptr<Sqlite3Db> db, ChangesetWriter &writer, bool &first )
734✔
474
{
475
  std::string sqlInserted = sqlFindInserted( tableName, tbl, reverse );
734✔
476
  Sqlite3Stmt statementI;
734✔
477
  statementI.prepare( db, "%s", sqlInserted.c_str() );
734✔
478
  int rc;
479
  while ( SQLITE_ROW == ( rc = sqlite3_step( statementI.get() ) ) )
1,016✔
480
  {
481
    if ( first )
282✔
482
    {
483
      ChangesetTable chTable = schemaToChangesetTable( tableName, tbl );
161✔
484
      writer.beginTable( chTable );
161✔
485
      first = false;
161✔
486
    }
161✔
487

488
    ChangesetEntry e;
282✔
489
    e.op = reverse ? ChangesetEntry::OpDelete : ChangesetEntry::OpInsert;
282✔
490

491
    size_t numColumns = tbl.columns.size();
282✔
492
    for ( size_t i = 0; i < numColumns; ++i )
1,280✔
493
    {
494
      Sqlite3Value v( sqlite3_column_value( statementI.get(), static_cast<int>( i ) ) );
998✔
495
      if ( reverse )
998✔
496
        e.oldValues.push_back( changesetValue( v.value() ) );
146✔
497
      else
498
        e.newValues.push_back( changesetValue( v.value() ) );
852✔
499
    }
998✔
500

501
    writer.writeEntry( e );
282✔
502
  }
282✔
503
  if ( rc != SQLITE_DONE )
734✔
504
  {
UNCOV
505
    logSqliteError( context, db, "Failed to write information about inserted rows in table " + tableName );
×
506
  }
507
}
734✔
508

509
static void handleUpdated( const Context *context, const std::string &tableName, const TableSchema &tbl, std::shared_ptr<Sqlite3Db> db, ChangesetWriter &writer, bool &first )
367✔
510
{
511
  std::string sqlModified = sqlFindModified( tableName, tbl );
367✔
512

513
  Sqlite3Stmt statement;
367✔
514
  statement.prepare( db, "%s", sqlModified.c_str() );
367✔
515
  int rc;
516
  while ( SQLITE_ROW == ( rc = sqlite3_step( statement.get() ) ) )
441✔
517
  {
518
    /*
519
    ** Within the old.* record associated with an UPDATE change, all fields
520
    ** associated with table columns that are not PRIMARY KEY columns and are
521
    ** not modified by the UPDATE change are set to "undefined". Other fields
522
    ** are set to the values that made up the row before the UPDATE that the
523
    ** change records took place. Within the new.* record, fields associated
524
    ** with table columns modified by the UPDATE change contain the new
525
    ** values. Fields associated with table columns that are not modified
526
    ** are set to "undefined".
527
    */
528

529
    ChangesetEntry e;
74✔
530
    e.op = ChangesetEntry::OpUpdate;
74✔
531

532
    bool hasUpdates = false;
74✔
533
    size_t numColumns = tbl.columns.size();
74✔
534
    for ( size_t i = 0; i < numColumns; ++i )
357✔
535
    {
536
      Sqlite3Value v1( sqlite3_column_value( statement.get(), static_cast<int>( i + numColumns ) ) );
283✔
537
      Sqlite3Value v2( sqlite3_column_value( statement.get(), static_cast<int>( i ) ) );
283✔
538
      bool pkey = tbl.columns[i].isPrimaryKey;
283✔
539
      bool updated = ( v1 != v2 );
283✔
540
      if ( updated )
283✔
541
      {
542
        // Let's do a secondary check for some column types to avoid false positives, for example
543
        // multiple different string representations could be used for a single datetime value,
544
        // see "Time Values" section in https://sqlite.org/lang_datefunc.html
545
        // Use strftime() to take into account fractional seconds
546
        if ( tbl.columns[i].type == TableColumnType::DATETIME )
86✔
547
        {
548
          Sqlite3Stmt stmtDatetime;
8✔
549
          stmtDatetime.prepare( db, "SELECT STRFTIME('%%Y-%%m-%%d %%H:%%M:%%f', ?1) IS NOT STRFTIME('%%Y-%%m-%%d %%H:%%M:%%f', ?2)" );
8✔
550
          sqlite3_bind_value( stmtDatetime.get(), 1, v1.value() );
8✔
551
          sqlite3_bind_value( stmtDatetime.get(), 2, v2.value() );
8✔
552
          int res = sqlite3_step( stmtDatetime.get() );
8✔
553
          if ( SQLITE_ROW == res )
8✔
554
          {
555
            updated = sqlite3_column_int( stmtDatetime.get(), 0 );
8✔
556
          }
UNCOV
557
          else if ( SQLITE_DONE != res )
×
558
          {
UNCOV
559
            logSqliteError( context, db, "Failed to write information about updated rows in table " + tableName );
×
560
          }
561
        }
8✔
562

563
        if ( updated )
86✔
564
        {
565
          hasUpdates = true;
84✔
566
        }
567
      }
568
      e.oldValues.push_back( ( pkey || updated ) ? changesetValue( v1.value() ) : Value() );
441✔
569
      e.newValues.push_back( updated ? changesetValue( v2.value() ) : Value() );
283✔
570
    }
283✔
571

572
    if ( hasUpdates )
74✔
573
    {
574
      if ( first )
72✔
575
      {
576
        ChangesetTable chTable = schemaToChangesetTable( tableName, tbl );
41✔
577
        writer.beginTable( chTable );
41✔
578
        first = false;
41✔
579
      }
41✔
580

581
      writer.writeEntry( e );
72✔
582
    }
583
  }
74✔
584
  if ( rc != SQLITE_DONE )
367✔
585
  {
UNCOV
586
    logSqliteError( context, db, "Failed to write information about inserted rows in table " + tableName );
×
587
  }
588
}
367✔
589

590
void SqliteDriver::createChangeset( ChangesetWriter &writer )
261✔
591
{
592
  std::vector<std::string> tablesBase = listTables( false );
261✔
593
  std::vector<std::string> tablesModified = listTables( true );
261✔
594

595
  if ( tablesBase != tablesModified )
261✔
596
  {
597
    throw GeoDiffException( "Table names are not matching between the input databases.\n"
598
                            "Base:     " + concatNames( tablesBase ) + "\n" +
6✔
599
                            "Modified: " + concatNames( tablesModified ) );
9✔
600
  }
601

602
  for ( const std::string &tableName : tablesBase )
627✔
603
  {
604
    TableSchema tbl = tableSchema( tableName );
375✔
605
    TableSchema tblNew = tableSchema( tableName, true );
375✔
606

607
    // test that table schema in the modified is the same
608
    if ( tbl != tblNew )
375✔
609
    {
610
      if ( !tbl.compareWithBaseTypes( tblNew ) )
18✔
611
        throw GeoDiffException( "GeoPackage Table schemas are not the same for table: " + tableName );
6✔
612
    }
613

614
    if ( !tbl.hasPrimaryKey() )
369✔
615
      continue;  // ignore tables without primary key - they can't be compared properly
2✔
616

617
    bool first = true;
367✔
618

619
    handleInserted( context(), tableName, tbl, false, mDb, writer, first );  // INSERT
367✔
620
    handleInserted( context(), tableName, tbl, true, mDb, writer, first );   // DELETE
367✔
621
    handleUpdated( context(), tableName, tbl, mDb, writer, first );          // UPDATE
367✔
622
  }
383✔
623

624
}
270✔
625

626
static std::string sqlForInsert( const std::string &tableName, const TableSchema &tbl )
125✔
627
{
628
  /*
629
   * For a table defined like this: CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
630
   *
631
   * INSERT INTO x (a, b, c, d) VALUES (?, ?, ?, ?)
632
   */
633

634
  std::string sql;
125✔
635
  sql += sqlitePrintf( "INSERT INTO \"%w\" (", tableName.c_str() );
125✔
636
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
575✔
637
  {
638
    if ( i > 0 )
450✔
639
      sql += ", ";
325✔
640
    sql += sqlitePrintf( "\"%w\"", tbl.columns[i].name.c_str() );
450✔
641
  }
642
  sql += ") VALUES (";
125✔
643
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
575✔
644
  {
645
    if ( i > 0 )
450✔
646
      sql += ", ";
325✔
647
    sql += "?";
450✔
648
  }
649
  sql += ")";
125✔
650
  return sql;
125✔
UNCOV
651
}
×
652

653
static std::string sqlForUpdate( const std::string &tableName, const TableSchema &tbl )
125✔
654
{
655
  /*
656
  ** For a table defined like this: CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
657
  **
658
  **     UPDATE x SET
659
  **     a = CASE WHEN ?2  THEN ?3  ELSE a END,
660
  **     b = CASE WHEN ?5  THEN ?6  ELSE b END,
661
  **     c = CASE WHEN ?8  THEN ?9  ELSE c END,
662
  **     d = CASE WHEN ?11 THEN ?12 ELSE d END
663
  **     WHERE a = ?1 AND c = ?7 AND (?13 OR
664
  **       (?5==0 OR b IS ?4) AND (?11==0 OR d IS ?10) AND
665
  **     )
666
  **
667
  ** For each column in the table, there are three variables to bind:
668
  **
669
  **     ?(i*3+1)    The old.* value of the column, if any.
670
  **     ?(i*3+2)    A boolean flag indicating that the value is being modified.
671
  **     ?(i*3+3)    The new.* value of the column, if any.
672
  */
673

674
  std::string sql;
125✔
675
  sql += sqlitePrintf( "UPDATE \"%w\" SET ", tableName.c_str() );
125✔
676

677
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
575✔
678
  {
679
    if ( i > 0 )
450✔
680
      sql += ", ";
325✔
681
    sql += sqlitePrintf( "\"%w\" = CASE WHEN ?%d THEN ?%d ELSE \"%w\" END", tbl.columns[i].name.c_str(), i * 3 + 2, i * 3 + 3, tbl.columns[i].name.c_str() );
450✔
682
  }
683
  sql += " WHERE ";
125✔
684
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
575✔
685
  {
686
    if ( i > 0 )
450✔
687
      sql += " AND ";
325✔
688
    if ( tbl.columns[i].isPrimaryKey )
450✔
689
      sql += sqlitePrintf( " \"%w\" = ?%d ", tbl.columns[i].name.c_str(), i * 3 + 1 );
125✔
690
    else if ( tbl.columns[i].type.baseType == TableColumnType::DATETIME )
325✔
691
    {
692
      // compare date/time values using datetime() because they may have
693
      // multiple equivalent string representations (see #143)
694
      sql += sqlitePrintf( " ( ?%d = 0 OR STRFTIME('%%Y-%%m-%%d %%H:%%M:%%f', \"%w\") IS STRFTIME('%%Y-%%m-%%d %%H:%%M:%%f', ?%d) ) ", i * 3 + 2, tbl.columns[i].name.c_str(), i * 3 + 1 );
4✔
695
    }
696
    else
697
      sql += sqlitePrintf( " ( ?%d = 0 OR \"%w\" IS ?%d ) ", i * 3 + 2, tbl.columns[i].name.c_str(), i * 3 + 1 );
321✔
698
  }
699

700
  return sql;
125✔
UNCOV
701
}
×
702

703
static std::string sqlForDelete( const std::string &tableName, const TableSchema &tbl )
125✔
704
{
705
  /*
706
   * For a table defined like this: CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
707
   *
708
   * DELETE FROM x WHERE a = ? AND b IS ? AND c = ? AND d IS ?
709
   */
710

711
  std::string sql;
125✔
712
  sql += sqlitePrintf( "DELETE FROM \"%w\" WHERE ", tableName.c_str() );
125✔
713
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
575✔
714
  {
715
    if ( i > 0 )
450✔
716
      sql += " AND ";
325✔
717
    if ( tbl.columns[i].isPrimaryKey )
450✔
718
      sql += sqlitePrintf( "\"%w\" = ?", tbl.columns[i].name.c_str() );
125✔
719
    else if ( tbl.columns[i].type.baseType == TableColumnType::DATETIME )
325✔
720
    {
721
      // compare date/time values using strftime() because otherwise
722
      // fractional seconds will be lost
723
      sql += sqlitePrintf( "STRFTIME('%%Y-%%m-%%d %%H:%%M:%%f', \"%w\") IS STRFTIME('%%Y-%%m-%%d %%H:%%M:%%f', ?)", tbl.columns[i].name.c_str() );
4✔
724
    }
725
    else
726
      sql += sqlitePrintf( "\"%w\" IS ?", tbl.columns[i].name.c_str() );
321✔
727
  }
728
  return sql;
125✔
UNCOV
729
}
×
730

731
static void bindValue( sqlite3_stmt *stmt, int index, const Value &v )
1,152✔
732
{
733
  int rc;
734
  if ( v.type() == Value::TypeInt )
1,152✔
735
    rc = sqlite3_bind_int64( stmt, index, v.getInt() );
507✔
736
  else if ( v.type() == Value::TypeDouble )
645✔
737
    rc = sqlite3_bind_double( stmt, index, v.getDouble() );
11✔
738
  else if ( v.type() == Value::TypeNull )
634✔
739
    rc = sqlite3_bind_null( stmt, index );
76✔
740
  else if ( v.type() == Value::TypeText )
558✔
741
    rc = sqlite3_bind_text( stmt, index, v.getString().c_str(), -1, SQLITE_TRANSIENT );
357✔
742
  else if ( v.type() == Value::TypeBlob )
201✔
743
    rc = sqlite3_bind_blob( stmt, index, v.getString().c_str(), ( int ) v.getString().size(), SQLITE_TRANSIENT );
201✔
744
  else
UNCOV
745
    throw GeoDiffException( "unexpected bind type" );
×
746

747
  if ( rc != SQLITE_OK )
1,152✔
748
  {
UNCOV
749
    throw GeoDiffException( "bind failed" );
×
750
  }
751
}
1,152✔
752

753

754
ChangeApplyResult SqliteDriver::applyChange( SqliteChangeApplyState &state, const ChangesetEntry &entry )
316✔
755
{
756
  std::string tableName = entry.table->name;
316✔
757

758
  if ( startsWith( tableName, "gpkg_" ) ) // skip any changes to GPKG meta tables
632✔
759
    return ChangeApplyResult::Skipped;
4✔
760

761
  if ( context()->isTableSkipped( tableName ) ) // skip table if necessary
312✔
762
    return ChangeApplyResult::Skipped;
6✔
763

764
  if ( state.tableState.count( tableName ) == 0 )
306✔
765
  {
766
    TableSchema schema = tableSchema( tableName );
125✔
767

768
    if ( schema.columns.size() == 0 )
125✔
UNCOV
769
      throw GeoDiffException( "No such table: " + tableName );
×
770

771
    if ( schema.columns.size() != entry.table->columnCount() )
125✔
UNCOV
772
      throw GeoDiffException( "Wrong number of columns for table: " + tableName );
×
773

774
    for ( size_t i = 0; i < entry.table->columnCount(); ++i )
575✔
775
    {
776
      if ( schema.columns[i].isPrimaryKey != entry.table->primaryKeys[i] )
450✔
UNCOV
777
        throw GeoDiffException( "Mismatch of primary keys in table: " + tableName );
×
778
    }
779

780
    SqliteChangeApplyState::TableState &tbl = state.tableState[tableName];
125✔
781
    tbl.schema = schema;
125✔
782

783
    tbl.stmtInsert.prepare( mDb, sqlForInsert( tableName, schema ) );
125✔
784
    tbl.stmtUpdate.prepare( mDb, sqlForUpdate( tableName, schema ) );
125✔
785
    tbl.stmtDelete.prepare( mDb, sqlForDelete( tableName, schema ) );
125✔
786
  }
125✔
787
  SqliteChangeApplyState::TableState &tbl = state.tableState[tableName];
306✔
788

789
  if ( entry.op == SQLITE_INSERT )
306✔
790
  {
791
    sqlite3_reset( tbl.stmtInsert.get() );
165✔
792
    for ( size_t i = 0; i < tbl.schema.columns.size(); ++i )
765✔
793
    {
794
      const Value &v = entry.newValues[i];
600✔
795
      bindValue( tbl.stmtInsert.get(), static_cast<int>( i ) + 1, v );
600✔
796
    }
797
    int res = sqlite3_step( tbl.stmtInsert.get() );
165✔
798
    if ( res == SQLITE_CONSTRAINT )
165✔
799
      return ChangeApplyResult::ConstraintConflict;
11✔
800
    else if ( res != SQLITE_DONE )
154✔
801
    {
802
      logApplyConflict( "insert_failed", entry, true );
×
UNCOV
803
      throw GeoDiffException( "SQLite error in INSERT" );
×
804
    }
805
    else if ( sqlite3_changes( mDb->get() ) != 1 )
154✔
UNCOV
806
      throw GeoDiffException( "Nothing inserted (this should never happen)" );
×
807
  }
808
  else if ( entry.op == SQLITE_UPDATE )
141✔
809
  {
810
    sqlite3_reset( tbl.stmtUpdate.get() );
85✔
811
    for ( size_t i = 0; i < tbl.schema.columns.size(); ++i )
397✔
812
    {
813
      const Value &vOld = entry.oldValues[i];
312✔
814
      const Value &vNew = entry.newValues[i];
312✔
815
      sqlite3_bind_int( tbl.stmtUpdate.get(), static_cast<int>( i ) * 3 + 2, vNew.type() != Value::TypeUndefined );
312✔
816
      if ( vOld.type() != Value::TypeUndefined )
312✔
817
        bindValue( tbl.stmtUpdate.get(), static_cast<int>( i ) * 3 + 1, vOld );
215✔
818
      if ( vNew.type() != Value::TypeUndefined )
312✔
819
        bindValue( tbl.stmtUpdate.get(), static_cast<int>( i ) * 3 + 3, vNew );
130✔
820
    }
821
    int res = sqlite3_step( tbl.stmtUpdate.get() );
85✔
822
    if ( res == SQLITE_CONSTRAINT )
85✔
UNCOV
823
      return ChangeApplyResult::ConstraintConflict;
×
824
    else if ( res != SQLITE_DONE )
85✔
825
    {
826
      logApplyConflict( "update_failed", entry, true );
×
UNCOV
827
      throw GeoDiffException( "SQLite error in UPDATE" );
×
828
    }
829
    else if ( sqlite3_changes( mDb->get() ) == 0 )
85✔
830
    {
831
      // either the row with such pkey does not exist or its data have been modified
832
      logApplyConflict( "update_nothing", entry );
2✔
833
      return ChangeApplyResult::NoChange;
2✔
834
    }
835
  }
836
  else if ( entry.op == SQLITE_DELETE )
56✔
837
  {
838
    sqlite3_reset( tbl.stmtDelete.get() );
56✔
839
    for ( size_t i = 0; i < tbl.schema.columns.size(); ++i )
263✔
840
    {
841
      const Value &v = entry.oldValues[i];
207✔
842
      bindValue( tbl.stmtDelete.get(), static_cast<int>( i ) + 1, v );
207✔
843
    }
844
    int res = sqlite3_step( tbl.stmtDelete.get() );
56✔
845
    if ( res == SQLITE_CONSTRAINT )
56✔
UNCOV
846
      return ChangeApplyResult::ConstraintConflict;
×
847
    else if ( res != SQLITE_DONE )
56✔
848
    {
849
      logApplyConflict( "delete_failed", entry, true );
×
UNCOV
850
      throw GeoDiffException( "SQLite error in DELETE" );
×
851
    }
852
    else if ( sqlite3_changes( mDb->get() ) == 0 )
56✔
853
    {
854
      // either the row with such pkey does not exist or its data have been modified
855
      logApplyConflict( "delete_nothing", entry );
1✔
856
      return ChangeApplyResult::NoChange;
1✔
857
    }
858
  }
859
  else
UNCOV
860
    throw GeoDiffException( "Unexpected operation" );
×
861

862
  return ChangeApplyResult::Applied;
292✔
863
}
316✔
864

865

866
void SqliteDriver::applyChangeset( ChangesetReader &reader )
99✔
867
{
868
  TableSchema tbl;
99✔
869

870
  // this will acquire DB mutex and release it when the function ends (or when an exception is thrown)
871
  Sqlite3DbMutexLocker dbMutexLocker( mDb );
99✔
872

873
  // start transaction!
874
  Sqlite3SavepointTransaction savepointTransaction( context(), mDb );
99✔
875

876
  // Defer verifying foreign key constraints until end of transaction. This
877
  // only applies inside our transaction, so we don't need to reset it.
878
  Sqlite3Stmt statement;
99✔
879
  statement.prepare( mDb, "pragma defer_foreign_keys = 1" );
99✔
880
  int rc = sqlite3_step( statement.get() );
99✔
881
  if ( SQLITE_DONE != rc )
99✔
UNCOV
882
    logSqliteError( context(), mDb, "Failed to defer foreign key checks" );
×
883
  statement.close();
99✔
884

885
  // get all triggers sql commands
886
  // that we do not recognize (gpkg triggers are filtered)
887
  std::vector<std::string> triggerNames;
99✔
888
  std::vector<std::string> triggerCmds;
99✔
889
  sqliteTriggers( context(), mDb, triggerNames, triggerCmds );
99✔
890

891
  for ( const std::string &name : triggerNames )
101✔
892
  {
893
    statement.prepare( mDb, "drop trigger '%q'", name.c_str() );
2✔
894
    rc = sqlite3_step( statement.get() );
2✔
895
    if ( SQLITE_DONE != rc )
2✔
896
    {
UNCOV
897
      logSqliteError( context(), mDb, "Failed to drop trigger " + name );
×
898
    }
899
    statement.close();
2✔
900
  }
901

902
  int unrecoverableConflictCount = 0;
99✔
903
  std::vector<ChangesetEntry> conflictingEntries;
99✔
904
  ChangesetEntry entry;
99✔
905
  SqliteChangeApplyState state;
99✔
906
  std::unordered_map<std::string, std::unique_ptr<ChangesetTable>> tableCopies;
99✔
907
  while ( reader.nextEntry( entry ) )
406✔
908
  {
909
    ChangeApplyResult res = applyChange( state, entry );
307✔
910
    switch ( res )
307✔
911
    {
912
      case ChangeApplyResult::Applied:
295✔
913
      case ChangeApplyResult::Skipped:
914
        break; // Applied correctly, continue onward.
295✔
915
      case ChangeApplyResult::ConstraintConflict:
9✔
916
        // Ordering conflict found, handle later.
917
        // Effectively copying the entry isn't simple, since ChangesetReader is
918
        // happy to change entry.table under our feet. We need to copy the
919
        // table object, ideally only keeping one per table.
920
        if ( tableCopies.count( entry.table->name ) == 0 )
9✔
921
          // cppcheck-suppress stlFindInsert
922
          tableCopies[entry.table->name] = std::unique_ptr<ChangesetTable>( new ChangesetTable( *entry.table ) );
9✔
923
        entry.table = tableCopies[entry.table->name].get();
9✔
924
        conflictingEntries.push_back( entry );
9✔
925
        break;
9✔
926
      case ChangeApplyResult::NoChange:
3✔
927
        unrecoverableConflictCount++; // Other issue, will throw at the end.
3✔
928
        break;
3✔
929
    }
930
  }
931

932
  // Applying some entries may fail due to constraints, since they require the
933
  // entries to be in some specific, unknown order. To work around this, we
934
  // retry applying the conflicting entries until either we apply them all or we
935
  // get stuck.
936
  std::vector<ChangesetEntry> newConflictingEntries;
196✔
937
  while ( conflictingEntries.size() > 0 )
103✔
938
  {
939
    for ( const ChangesetEntry &centry : conflictingEntries )
16✔
940
    {
941
      ChangeApplyResult res = applyChange( state, centry );
9✔
942
      switch ( res )
9✔
943
      {
944
        case ChangeApplyResult::Applied:
7✔
945
        case ChangeApplyResult::Skipped:
946
          break; // Applied correctly, don't put it in the new list.
7✔
947
        case ChangeApplyResult::ConstraintConflict:
2✔
948
          newConflictingEntries.push_back( centry ); // Still conflicting, keep in list.
2✔
949
          break;
2✔
950
        case ChangeApplyResult::NoChange:
×
951
          unrecoverableConflictCount++; // Other issue, will throw at the end.
×
UNCOV
952
          break;
×
953
      }
954
    }
955

956
    // If we haven't been able to apply any of the conflicting entries this
957
    // loop, then these conflicts can't be resolved by reordering entries.
958
    if ( newConflictingEntries.size() == conflictingEntries.size() )
7✔
959
    {
960
      for ( const ChangesetEntry &centry : conflictingEntries )
4✔
961
        logApplyConflict( "unresolvable_conflict", centry );
4✔
962
      throw GeoDiffConflictsException( "Could not resolve dependencies in constraint conflicts." );
6✔
963
    }
964
    conflictingEntries = newConflictingEntries;
5✔
965
    newConflictingEntries.clear();
5✔
966
  }
967

968
  // recreate triggers
969
  for ( const std::string &cmd : triggerCmds )
98✔
970
  {
971
    statement.prepare( mDb, "%s", cmd.c_str() );
2✔
972
    if ( SQLITE_DONE != sqlite3_step( statement.get() ) )
2✔
973
    {
UNCOV
974
      logSqliteError( context(), mDb, "Failed to recreate trigger using SQL \"" + cmd + "\"" );
×
975
    }
976
    statement.close();
2✔
977
  }
978

979
  if ( !unrecoverableConflictCount )
96✔
980
  {
981
    savepointTransaction.commitChanges();
93✔
982
  }
983
  else
984
  {
985
    throw GeoDiffConflictsException( "Conflicts encountered while applying changes! Total " + std::to_string( unrecoverableConflictCount ) );
3✔
986
  }
987
}
171✔
988

989

990
static void addGpkgCrsDefinition( std::shared_ptr<Sqlite3Db> db, const CrsDefinition &crs )
22✔
991
{
992
  // gpkg_spatial_ref_sys
993
  //   srs_name TEXT NOT NULL, srs_id INTEGER NOT NULL PRIMARY KEY,
994
  //   organization TEXT NOT NULL, organization_coordsys_id INTEGER NOT NULL,
995
  //   definition  TEXT NOT NULL, description TEXT
996

997
  Sqlite3Stmt stmtCheck;
22✔
998
  stmtCheck.prepare( db, "select count(*) from gpkg_spatial_ref_sys where srs_id = %d;", crs.srsId );
22✔
999
  int res = sqlite3_step( stmtCheck.get() );
22✔
1000
  if ( res != SQLITE_ROW )
22✔
1001
  {
UNCOV
1002
    throwSqliteError( db->get(), "Failed to access gpkg_spatial_ref_sys table" );
×
1003
  }
1004

1005
  if ( sqlite3_column_int( stmtCheck.get(), 0 ) )
22✔
1006
    return;  // already there
22✔
1007

1008
  Sqlite3Stmt stmt;
×
1009
  stmt.prepare( db, "INSERT INTO gpkg_spatial_ref_sys VALUES ('%q:%d', %d, '%q', %d, '%q', '')",
×
UNCOV
1010
                crs.authName.c_str(), crs.authCode, crs.srsId, crs.authName.c_str(), crs.authCode,
×
1011
                crs.wkt.c_str() );
1012
  res = sqlite3_step( stmt.get() );
×
UNCOV
1013
  if ( res != SQLITE_DONE )
×
1014
  {
UNCOV
1015
    throwSqliteError( db->get(), "Failed to insert CRS to gpkg_spatial_ref_sys table" );
×
1016
  }
1017
}
22✔
1018

1019
static void addGpkgSpatialTable( std::shared_ptr<Sqlite3Db> db, const TableSchema &tbl, const Extent &extent )
22✔
1020
{
1021
  size_t i = tbl.geometryColumn();
22✔
1022
  if ( i == SIZE_MAX )
22✔
UNCOV
1023
    throw GeoDiffException( "Adding non-spatial tables is not supported: " + tbl.name );
×
1024

1025
  const TableColumnInfo &col = tbl.columns[i];
22✔
1026
  std::string geomColumn = col.name;
22✔
1027
  std::string geomType = col.geomType;
22✔
1028
  int srsId = col.geomSrsId;
22✔
1029
  bool hasZ = col.geomHasZ;
22✔
1030
  bool hasM = col.geomHasM;
22✔
1031

1032
  // gpkg_contents
1033
  //   table_name TEXT NOT NULL PRIMARY KEY, data_type TEXT NOT NULL,
1034
  //   identifier TEXT, description TEXT DEFAULT '',
1035
  //   last_change DATETIME NOT NULL DEFAULT (...),
1036
  //   min_x DOUBLE, min_y DOUBLE, max_x DOUBLE, max_y DOUBLE,
1037
  //   srs_id INTEGER
1038

1039
  Sqlite3Stmt stmt;
22✔
1040
  stmt.prepare( db, "INSERT INTO gpkg_contents (table_name, data_type, identifier, min_x, min_y, max_x, max_y, srs_id) "
22✔
1041
                "VALUES ('%q', 'features', '%q', %f, %f, %f, %f, %d)",
1042
                tbl.name.c_str(), tbl.name.c_str(), extent.minX, extent.minY, extent.maxX, extent.maxY, srsId );
22✔
1043
  int res = sqlite3_step( stmt.get() );
22✔
1044
  if ( res != SQLITE_DONE )
22✔
1045
  {
UNCOV
1046
    throwSqliteError( db->get(), "Failed to insert row to gpkg_contents table" );
×
1047
  }
1048

1049
  // gpkg_geometry_columns
1050
  //   table_name TEXT NOT NULL, column_name TEXT NOT NULL,
1051
  //   geometry_type_name TEXT NOT NULL, srs_id INTEGER NOT NULL,
1052
  //   z TINYINT NOT NULL,m TINYINT NOT NULL
1053

1054
  Sqlite3Stmt stmtGeomCol;
22✔
1055
  stmtGeomCol.prepare( db, "INSERT INTO gpkg_geometry_columns VALUES ('%q', '%q', '%q', %d, %d, %d)",
22✔
1056
                       tbl.name.c_str(), geomColumn.c_str(), geomType.c_str(), srsId, hasZ, hasM );
1057
  res = sqlite3_step( stmtGeomCol.get() );
22✔
1058
  if ( res != SQLITE_DONE )
22✔
1059
  {
UNCOV
1060
    throwSqliteError( db->get(), "Failed to insert row to gpkg_geometry_columns table" );
×
1061
  }
1062
}
22✔
1063

1064
void SqliteDriver::createTables( const std::vector<TableSchema> &tables )
14✔
1065
{
1066
  // currently we always create geopackage meta tables. Maybe in the future we can skip
1067
  // that if there is a reason, and have that optional if none of the tables are spatial.
1068
  Sqlite3Stmt stmt1;
14✔
1069
  stmt1.prepare( mDb, "SELECT InitSpatialMetadata('main');" );
14✔
1070
  int res = sqlite3_step( stmt1.get() );
14✔
1071
  if ( res != SQLITE_ROW )
14✔
1072
  {
UNCOV
1073
    throwSqliteError( mDb->get(), "Failure initializing spatial metadata" );
×
1074
  }
1075

1076
  for ( const TableSchema &tbl : tables )
37✔
1077
  {
1078
    if ( startsWith( tbl.name, "gpkg_" ) )
46✔
UNCOV
1079
      continue;
×
1080

1081
    if ( tbl.geometryColumn() != SIZE_MAX )
23✔
1082
    {
1083
      addGpkgCrsDefinition( mDb, tbl.crs );
22✔
1084
      addGpkgSpatialTable( mDb, tbl, Extent() );   // TODO: is it OK to set zeros?
22✔
1085
    }
1086

1087
    std::string sql, pkeyCols, columns;
23✔
1088
    for ( const TableColumnInfo &c : tbl.columns )
103✔
1089
    {
1090
      if ( !columns.empty() )
80✔
1091
        columns += ", ";
57✔
1092

1093
      columns += sqlitePrintf( "\"%w\" %s", c.name.c_str(), c.type.dbType.c_str() );
80✔
1094

1095
      if ( c.isNotNull )
80✔
1096
        columns += " NOT NULL";
24✔
1097

1098
      // we have also c.isAutoIncrement, but the SQLite AUTOINCREMENT keyword only applies
1099
      // to primary keys, and according to the docs, ordinary tables with INTEGER PRIMARY KEY column
1100
      // (which becomes alias to ROWID) does auto-increment, and AUTOINCREMENT just prevents
1101
      // reuse of ROWIDs from previously deleted rows.
1102
      // See https://sqlite.org/autoinc.html
1103

1104
      if ( c.isPrimaryKey )
80✔
1105
      {
1106
        if ( !pkeyCols.empty() )
22✔
UNCOV
1107
          pkeyCols += ", ";
×
1108
        pkeyCols += sqlitePrintf( "\"%w\"", c.name.c_str() );
22✔
1109
      }
1110
    }
1111

1112
    sql = sqlitePrintf( "CREATE TABLE \"%w\".\"%w\" (", "main", tbl.name.c_str() );
23✔
1113
    if ( !columns.empty() )
23✔
1114
    {
1115
      sql += columns;
23✔
1116
    }
1117
    if ( !pkeyCols.empty() )
23✔
1118
    {
1119
      sql += ", PRIMARY KEY (" + pkeyCols + ")";
22✔
1120
    }
1121
    sql += ");";
23✔
1122

1123
    Sqlite3Stmt stmt;
23✔
1124
    stmt.prepare( mDb, sql );
23✔
1125
    if ( sqlite3_step( stmt.get() ) != SQLITE_DONE )
23✔
1126
    {
UNCOV
1127
      throwSqliteError( mDb->get(), "Failure creating table: " + tbl.name );
×
1128
    }
1129
  }
23✔
1130
}
14✔
1131

1132

1133
void SqliteDriver::dumpData( ChangesetWriter &writer, bool useModified )
14✔
1134
{
1135
  std::string dbName = databaseName( useModified );
14✔
1136
  std::vector<std::string> tables = listTables();
14✔
1137
  for ( const std::string &tableName : tables )
45✔
1138
  {
1139
    TableSchema tbl = tableSchema( tableName, useModified );
31✔
1140
    if ( !tbl.hasPrimaryKey() )
31✔
1141
      continue;  // ignore tables without primary key - they can't be compared properly
1✔
1142

1143
    bool first = true;
30✔
1144
    Sqlite3Stmt statementI;
30✔
1145
    statementI.prepare( mDb, "SELECT * FROM \"%w\".\"%w\"", dbName.c_str(), tableName.c_str() );
30✔
1146
    int rc;
1147
    while ( SQLITE_ROW == ( rc = sqlite3_step( statementI.get() ) ) )
95✔
1148
    {
1149
      if ( first )
65✔
1150
      {
1151
        writer.beginTable( schemaToChangesetTable( tableName, tbl ) );
29✔
1152
        first = false;
29✔
1153
      }
1154

1155
      ChangesetEntry e;
65✔
1156
      e.op = ChangesetEntry::OpInsert;
65✔
1157
      size_t numColumns = tbl.columns.size();
65✔
1158
      for ( size_t i = 0; i < numColumns; ++i )
316✔
1159
      {
1160
        Sqlite3Value v( sqlite3_column_value( statementI.get(), static_cast<int>( i ) ) );
251✔
1161
        e.newValues.push_back( changesetValue( v.value() ) );
251✔
1162
      }
251✔
1163
      writer.writeEntry( e );
65✔
1164
    }
65✔
1165
    if ( rc != SQLITE_DONE )
30✔
1166
    {
UNCOV
1167
      logSqliteError( context(), mDb, "Failure dumping changeset" );
×
1168
    }
1169
  }
31✔
1170
}
14✔
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