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

MerginMaps / geodiff / 20332653776

18 Dec 2025 09:42AM UTC coverage: 88.298% (-1.7%) from 90.035%
20332653776

push

github

wonder-sk
Fix updating rows with microsecond timestamps in Postgres

1 of 1 new or added line in 1 file covered. (100.0%)

237 existing lines in 6 files now uncovered.

3584 of 4059 relevant lines covered (88.3%)

582.99 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 "sqliteutils.h"
14

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

18

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

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

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

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

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

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

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

100

101
///////
102

103

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

327
    //
328
    // get CRS information
329
    //
330

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

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

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

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

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

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

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

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

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

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

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

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

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

448
  return sql;
734✔
449
}
367✔
450

451

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

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

472
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✔
473
{
474
  std::string sqlInserted = sqlFindInserted( tableName, tbl, reverse );
734✔
475
  Sqlite3Stmt statementI;
734✔
476
  statementI.prepare( db, "%s", sqlInserted.c_str() );
734✔
477
  int rc;
478
  while ( SQLITE_ROW == ( rc = sqlite3_step( statementI.get() ) ) )
1,016✔
479
  {
480
    if ( first )
282✔
481
    {
482
      ChangesetTable chTable = schemaToChangesetTable( tableName, tbl );
161✔
483
      writer.beginTable( chTable );
161✔
484
      first = false;
161✔
485
    }
161✔
486

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

616
    bool first = true;
367✔
617

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

623
}
270✔
624

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

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

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

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

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

699
  return sql;
125✔
UNCOV
700
}
×
701

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

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

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

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

752

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

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

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

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

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

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

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

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

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

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

861
  return SqliteChangeApplyResult::Applied;
292✔
862
}
316✔
863

864

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

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

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

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

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

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

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

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

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

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

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

988

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1131

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

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

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