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

MerginMaps / geodiff / 3710958510

pending completion
3710958510

push

github

GitHub
allow python logger to set to None (#192)

3047 of 4066 relevant lines covered (74.94%)

409.27 hits per line

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

88.64
/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

14
#include <memory.h>
15
#include <iostream>
16

17

18
void SqliteDriver::logApplyConflict( const std::string &type, const ChangesetEntry &entry ) const
3✔
19
{
20
  context()->logger().warn( "CONFLICT: " + type + ":\n" + changesetEntryToJSON( entry ).dump( 2 ) );
3✔
21
}
3✔
22

23
/**
24
 * Wrapper around SQLite database wide mutex.
25
 */
26
class Sqlite3DbMutexLocker
27
{
28
  public:
29
    explicit Sqlite3DbMutexLocker( std::shared_ptr<Sqlite3Db> db )
74✔
30
      : mDb( db )
74✔
31
    {
32
      sqlite3_mutex_enter( sqlite3_db_mutex( mDb.get()->get() ) );
74✔
33
    }
74✔
34
    ~Sqlite3DbMutexLocker()
74✔
35
    {
36
      sqlite3_mutex_leave( sqlite3_db_mutex( mDb.get()->get() ) );
74✔
37
    }
74✔
38

39
  private:
40
    std::shared_ptr<Sqlite3Db> mDb;
41
};
42

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

62
    ~Sqlite3SavepointTransaction()
74✔
63
    {
64
      if ( mDb )
74✔
65
      {
66
        // we had some problems - roll back any pending changes
67
        if ( sqlite3_exec( mDb.get()->get(), "ROLLBACK TO changeset_apply", 0, 0, 0 ) != SQLITE_OK )
3✔
68
        {
69
          logSqliteError( mContext, mDb, "Unable to rollback savepoint transaction" );
×
70
        }
71
        if ( sqlite3_exec( mDb.get()->get(), "RELEASE changeset_apply", 0, 0, 0 ) != SQLITE_OK )
3✔
72
        {
73
          logSqliteError( mContext, mDb, "Unable to release savepoint" );
×
74
        }
75
      }
76
    }
74✔
77

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

90
  private:
91
    std::shared_ptr<Sqlite3Db> mDb;
92
    const Context *mContext;
93
};
94

95

96
///////
97

98

99
SqliteDriver::SqliteDriver( const Context *context )
315✔
100
  : Driver( context )
315✔
101
{
102
}
315✔
103

104
void SqliteDriver::open( const DriverParametersMap &conn )
302✔
105
{
106
  DriverParametersMap::const_iterator connBaseIt = conn.find( "base" );
302✔
107
  if ( connBaseIt == conn.end() )
302✔
108
    throw GeoDiffException( "Missing 'base' file" );
1✔
109

110
  DriverParametersMap::const_iterator connModifiedIt = conn.find( "modified" );
301✔
111
  mHasModified = connModifiedIt != conn.end();
301✔
112

113
  std::string base = connBaseIt->second;
301✔
114
  if ( !fileexists( base ) )
301✔
115
  {
116
    throw GeoDiffException( "Missing 'base' file when opening sqlite driver: " + base );
3✔
117
  }
118

119
  mDb = std::make_shared<Sqlite3Db>();
298✔
120
  if ( mHasModified )
298✔
121
  {
122
    std::string modified = connModifiedIt->second;
194✔
123

124
    if ( !fileexists( modified ) )
194✔
125
    {
126
      throw GeoDiffException( "Missing 'modified' file when opening sqlite driver: " + modified );
1✔
127
    }
128

129
    mDb->open( modified );
193✔
130

131
    Buffer sqlBuf;
193✔
132
    sqlBuf.printf( "ATTACH '%q' AS aux", base.c_str() );
193✔
133
    mDb->exec( sqlBuf );
193✔
134
  }
195✔
135
  else
136
  {
137
    mDb->open( base );
104✔
138
  }
139

140
  // GeoPackage triggers require few functions like ST_IsEmpty() to be registered
141
  // in order to be able to apply changesets
142
  if ( isGeoPackage( context(), mDb ) )
296✔
143
  {
144
    register_gpkg_extensions( mDb );
284✔
145
  }
146
}
301✔
147

148
void SqliteDriver::create( const DriverParametersMap &conn, bool overwrite )
13✔
149
{
150
  DriverParametersMap::const_iterator connBaseIt = conn.find( "base" );
13✔
151
  if ( connBaseIt == conn.end() )
13✔
152
    throw GeoDiffException( "Missing 'base' file" );
×
153

154
  std::string base = connBaseIt->second;
13✔
155

156
  if ( overwrite )
13✔
157
  {
158
    fileremove( base );  // remove if the file exists already
13✔
159
  }
160

161
  mDb = std::make_shared<Sqlite3Db>();
13✔
162
  mDb->create( base );
13✔
163

164
  // register geopackage related functions in the newly created sqlite database
165
  register_gpkg_extensions( mDb );
13✔
166
}
13✔
167

168
std::string SqliteDriver::databaseName( bool useModified )
1,112✔
169
{
170
  if ( mHasModified )
1,112✔
171
  {
172
    return useModified ? "main" : "aux";
900✔
173
  }
174
  else
175
  {
176
    if ( useModified )
212✔
177
      throw GeoDiffException( "'modified' table not open" );
×
178
    return "main";
212✔
179
  }
180
}
181

182
std::vector<std::string> SqliteDriver::listTables( bool useModified )
410✔
183
{
184
  std::string dbName = databaseName( useModified );
410✔
185
  std::vector<std::string> tableNames;
410✔
186
  std::string all_tables_sql = "SELECT name FROM " + dbName + ".sqlite_master\n"
410✔
187
                               " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
188
                               " ORDER BY name";
410✔
189
  Sqlite3Stmt statement;
410✔
190
  statement.prepare( mDb, "%s", all_tables_sql.c_str() );
410✔
191
  int rc;
192
  while ( SQLITE_ROW == ( rc = sqlite3_step( statement.get() ) ) )
5,798✔
193
  {
194
    const char *name = ( const char * )sqlite3_column_text( statement.get(), 0 );
5,388✔
195
    if ( !name )
5,388✔
196
      continue;
4,801✔
197

198
    std::string tableName( name );
5,388✔
199
    /* typically geopackage from ogr would have these (table name is simple)
200
    gpkg_contents
201
    gpkg_extensions
202
    gpkg_geometry_columns
203
    gpkg_ogr_contents
204
    gpkg_spatial_ref_sys
205
    gpkg_tile_matrix
206
    gpkg_tile_matrix_set
207
    rtree_simple_geometry_node
208
    rtree_simple_geometry_parent
209
    rtree_simple_geometry_rowid
210
    simple (or any other name(s) of layers)
211
    sqlite_sequence
212
    */
213

214
    // table handled by triggers trigger_*_feature_count_*
215
    if ( startsWith( tableName, "gpkg_" ) )
5,388✔
216
      continue;
2,815✔
217
    // table handled by triggers rtree_*_geometry_*
218
    if ( startsWith( tableName, "rtree_" ) )
2,573✔
219
      continue;
1,575✔
220
    // internal table for AUTOINCREMENT
221
    if ( tableName == "sqlite_sequence" )
998✔
222
      continue;
394✔
223

224
    if ( context()->isTableSkipped( tableName ) )
604✔
225
      continue;
17✔
226

227
    tableNames.push_back( tableName );
587✔
228
  }
5,388✔
229
  if ( rc != SQLITE_DONE )
410✔
230
  {
231
    logSqliteError( context(), mDb, "Failed to list SQLite tables" );
×
232
  }
233

234
  // result is ordered by name
235
  return tableNames;
820✔
236
}
410✔
237

238
bool tableExists( std::shared_ptr<Sqlite3Db> db, const std::string &tableName, const std::string &dbName )
1,342✔
239
{
240
  Sqlite3Stmt stmtHasGeomColumnsInfo;
1,342✔
241
  stmtHasGeomColumnsInfo.prepare( db, "SELECT name FROM \"%w\".sqlite_master WHERE type='table' "
1,342✔
242
                                  "AND name='%q'", dbName.c_str(), tableName.c_str() );
243
  return sqlite3_step( stmtHasGeomColumnsInfo.get() ) == SQLITE_ROW;
2,684✔
244
}
1,342✔
245

246
TableSchema SqliteDriver::tableSchema( const std::string &tableName,
672✔
247
                                       bool useModified )
248
{
249
  std::string dbName = databaseName( useModified );
672✔
250

251
  if ( !tableExists( mDb, tableName, dbName ) )
672✔
252
    throw GeoDiffException( "Table does not exist: " + tableName );
2✔
253

254
  TableSchema tbl;
670✔
255
  tbl.name = tableName;
670✔
256
  std::map<std::string, std::string> columnTypes;
670✔
257

258
  Sqlite3Stmt statement;
670✔
259
  statement.prepare( mDb, "PRAGMA '%q'.table_info('%q')", dbName.c_str(), tableName.c_str() );
670✔
260
  int rc;
261
  while ( SQLITE_ROW == ( rc = sqlite3_step( statement.get() ) ) )
3,219✔
262
  {
263
    const unsigned char *zName = sqlite3_column_text( statement.get(), 1 );
2,549✔
264
    if ( zName == nullptr )
2,549✔
265
      throw GeoDiffException( "NULL column name in table schema: " + tableName );
×
266

267
    TableColumnInfo columnInfo;
2,549✔
268
    columnInfo.name = ( const char * )zName;
2,549✔
269
    columnInfo.isNotNull = sqlite3_column_int( statement.get(), 3 );
2,549✔
270
    columnInfo.isPrimaryKey = sqlite3_column_int( statement.get(), 5 );
2,549✔
271
    columnTypes[columnInfo.name] = ( const char * ) sqlite3_column_text( statement.get(), 2 );
2,549✔
272

273
    tbl.columns.push_back( columnInfo );
2,549✔
274
  }
2,549✔
275
  if ( rc != SQLITE_DONE )
670✔
276
  {
277
    logSqliteError( context(), mDb, "Failed to get list columns for table " + tableName );
×
278
  }
279

280
  // check if the geometry columns table is present (it may not be if this is a "pure" sqlite file)
281
  if ( tableExists( mDb, "gpkg_geometry_columns", dbName ) )
670✔
282
  {
283
    //
284
    // get geometry column details (geometry type, whether it has Z/M values, CRS id)
285
    //
286

287
    int srsId = -1;
630✔
288
    Sqlite3Stmt stmtGeomCol;
630✔
289
    stmtGeomCol.prepare( mDb, "SELECT * FROM \"%w\".gpkg_geometry_columns WHERE table_name = '%q'", dbName.c_str(), tableName.c_str() );
630✔
290
    while ( SQLITE_ROW == ( rc = sqlite3_step( stmtGeomCol.get() ) ) )
1,230✔
291
    {
292
      const unsigned char *chrColumnName = sqlite3_column_text( stmtGeomCol.get(), 1 );
600✔
293
      const unsigned char *chrTypeName = sqlite3_column_text( stmtGeomCol.get(), 2 );
600✔
294
      if ( chrColumnName == nullptr )
600✔
295
        throw GeoDiffException( "NULL column name in gpkg_geometry_columns: " + tableName );
×
296
      if ( chrTypeName == nullptr )
600✔
297
        throw GeoDiffException( "NULL type name in gpkg_geometry_columns: " + tableName );
×
298

299
      std::string geomColName = ( const char * ) chrColumnName;
600✔
300
      std::string geomTypeName = ( const char * ) chrTypeName;
600✔
301
      srsId = sqlite3_column_int( stmtGeomCol.get(), 3 );
600✔
302
      bool hasZ = sqlite3_column_int( stmtGeomCol.get(), 4 );
600✔
303
      bool hasM = sqlite3_column_int( stmtGeomCol.get(), 5 );
600✔
304

305
      size_t i = tbl.columnFromName( geomColName );
600✔
306
      if ( i == SIZE_MAX )
600✔
307
        throw GeoDiffException( "Inconsistent entry in gpkg_geometry_columns - geometry column not found: " + geomColName );
×
308

309
      TableColumnInfo &col = tbl.columns[i];
600✔
310
      col.setGeometry( geomTypeName, srsId, hasM, hasZ );
600✔
311
    }
600✔
312
    if ( rc != SQLITE_DONE )
630✔
313
    {
314
      logSqliteError( context(), mDb, "Failed to get geometry column info for table " + tableName );
×
315
    }
316

317
    //
318
    // get CRS information
319
    //
320

321
    if ( srsId != -1 )
630✔
322
    {
323
      Sqlite3Stmt stmtCrs;
600✔
324
      stmtCrs.prepare( mDb, "SELECT * FROM \"%w\".gpkg_spatial_ref_sys WHERE srs_id = %d", dbName.c_str(), srsId );
600✔
325
      if ( SQLITE_ROW != sqlite3_step( stmtCrs.get() ) )
600✔
326
      {
327
        throwSqliteError( mDb->get(), "Unable to find entry in gpkg_spatial_ref_sys for srs_id = " + std::to_string( srsId ) );
×
328
      }
329

330
      const unsigned char *chrAuthName = sqlite3_column_text( stmtCrs.get(), 2 );
600✔
331
      const unsigned char *chrWkt = sqlite3_column_text( stmtCrs.get(), 4 );
600✔
332
      if ( chrAuthName == nullptr )
600✔
333
        throw GeoDiffException( "NULL auth name in gpkg_spatial_ref_sys: " + tableName );
×
334
      if ( chrWkt == nullptr )
600✔
335
        throw GeoDiffException( "NULL definition in gpkg_spatial_ref_sys: " + tableName );
×
336

337
      tbl.crs.srsId = srsId;
600✔
338
      tbl.crs.authName = ( const char * ) chrAuthName;
600✔
339
      tbl.crs.authCode = sqlite3_column_int( stmtCrs.get(), 3 );
600✔
340
      tbl.crs.wkt = ( const char * ) chrWkt;
600✔
341
    }
600✔
342
  }
630✔
343

344
  // update column types
345
  for ( auto const &it : columnTypes )
3,219✔
346
  {
347
    size_t i = tbl.columnFromName( it.first );
2,549✔
348
    TableColumnInfo &col = tbl.columns[i];
2,549✔
349
    tbl.columns[i].type = columnType( context(), it.second, Driver::SQLITEDRIVERNAME, col.isGeometry );
2,549✔
350

351
    if ( col.isPrimaryKey && ( lowercaseString( col.type.dbType ) == "integer" ) )
2,549✔
352
    {
353
      // sqlite uses auto-increment automatically for INTEGER PRIMARY KEY - https://sqlite.org/autoinc.html
354
      col.isAutoIncrement = true;
670✔
355
    }
356
  }
357

358
  return tbl;
1,340✔
359
}
672✔
360

361
/**
362
 * printf() with sqlite extensions - see https://www.sqlite.org/printf.html
363
 * for extra format options like %q or %Q
364
 */
365
static std::string sqlitePrintf( const char *zFormat, ... )
3,906✔
366
{
367
  va_list ap;
368
  va_start( ap, zFormat );
3,906✔
369
  char *zSql = sqlite3_vmprintf( zFormat, ap );
3,906✔
370
  va_end( ap );
3,906✔
371

372
  if ( zSql == nullptr )
3,906✔
373
  {
374
    throw GeoDiffException( "out of memory" );
×
375
  }
376
  std::string res = ( const char * )zSql;
3,906✔
377
  sqlite3_free( zSql );
3,906✔
378
  return res;
7,812✔
379
}
×
380

381
//! Constructs SQL query to get all rows that do not exist in the other table (used for insert and delete)
382
static std::string sqlFindInserted( const std::string &tableName, const TableSchema &tbl, bool reverse )
506✔
383
{
384
  std::string exprPk;
506✔
385
  for ( const TableColumnInfo &c : tbl.columns )
2,412✔
386
  {
387
    if ( c.isPrimaryKey )
1,906✔
388
    {
389
      if ( !exprPk.empty() )
506✔
390
        exprPk += " AND ";
×
391
      exprPk += sqlitePrintf( "\"%w\".\"%w\".\"%w\"=\"%w\".\"%w\".\"%w\"",
1,012✔
392
                              "main", tableName.c_str(), c.name.c_str(), "aux", tableName.c_str(), c.name.c_str() );
506✔
393
    }
394
  }
395

396
  std::string sql = sqlitePrintf( "SELECT * FROM \"%w\".\"%w\" WHERE NOT EXISTS ( SELECT 1 FROM \"%w\".\"%w\" WHERE %s)",
397
                                  reverse ? "aux" : "main", tableName.c_str(),
398
                                  reverse ? "main" : "aux", tableName.c_str(), exprPk.c_str() );
506✔
399
  return sql;
1,012✔
400
}
506✔
401

402
//! Constructs SQL query to get all modified rows for a single table
403
static std::string sqlFindModified( const std::string &tableName, const TableSchema &tbl )
253✔
404
{
405
  std::string exprPk;
253✔
406
  std::string exprOther;
253✔
407
  for ( const TableColumnInfo &c : tbl.columns )
1,206✔
408
  {
409
    if ( c.isPrimaryKey )
953✔
410
    {
411
      if ( !exprPk.empty() )
253✔
412
        exprPk += " AND ";
×
413
      exprPk += sqlitePrintf( "\"%w\".\"%w\".\"%w\"=\"%w\".\"%w\".\"%w\"",
506✔
414
                              "main", tableName.c_str(), c.name.c_str(), "aux", tableName.c_str(), c.name.c_str() );
253✔
415
    }
416
    else // not a primary key column
417
    {
418
      if ( !exprOther.empty() )
700✔
419
        exprOther += " OR ";
448✔
420

421
      exprOther += sqlitePrintf( "\"%w\".\"%w\".\"%w\" IS NOT \"%w\".\"%w\".\"%w\"",
1,400✔
422
                                 "main", tableName.c_str(), c.name.c_str(), "aux", tableName.c_str(), c.name.c_str() );
700✔
423
    }
424
  }
425
  std::string sql;
253✔
426

427
  if ( exprOther.empty() )
253✔
428
  {
429
    sql = sqlitePrintf( "SELECT * FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s",
2✔
430
                        "main", tableName.c_str(), "aux", tableName.c_str(), exprPk.c_str() );
1✔
431
  }
432
  else
433
  {
434
    sql = sqlitePrintf( "SELECT * FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%s)",
504✔
435
                        "main", tableName.c_str(), "aux", tableName.c_str(), exprPk.c_str(), exprOther.c_str() );
252✔
436
  }
437

438
  return sql;
506✔
439
}
253✔
440

441

442
static Value changesetValue( sqlite3_value *v )
972✔
443
{
444
  Value x;
972✔
445
  int type = sqlite3_value_type( v );
972✔
446
  if ( type == SQLITE_NULL )
972✔
447
    x.setNull();
109✔
448
  else if ( type == SQLITE_INTEGER )
863✔
449
    x.setInt( sqlite3_value_int64( v ) );
424✔
450
  else if ( type == SQLITE_FLOAT )
439✔
451
    x.setDouble( sqlite3_value_double( v ) );
15✔
452
  else if ( type == SQLITE_TEXT )
424✔
453
    x.setString( Value::TypeText, ( const char * )sqlite3_value_text( v ), sqlite3_value_bytes( v ) );
225✔
454
  else if ( type == SQLITE_BLOB )
199✔
455
    x.setString( Value::TypeBlob, ( const char * )sqlite3_value_blob( v ), sqlite3_value_bytes( v ) );
199✔
456
  else
457
    throw GeoDiffException( "Unexpected value type" );
×
458

459
  return x;
972✔
460
}
×
461

462
static void handleInserted( const Context *context, const std::string &tableName, const TableSchema &tbl, bool reverse, std::shared_ptr<Sqlite3Db> db, ChangesetWriter &writer, bool &first )
506✔
463
{
464
  std::string sqlInserted = sqlFindInserted( tableName, tbl, reverse );
506✔
465
  Sqlite3Stmt statementI;
506✔
466
  statementI.prepare( db, "%s", sqlInserted.c_str() );
506✔
467
  int rc;
468
  while ( SQLITE_ROW == ( rc = sqlite3_step( statementI.get() ) ) )
665✔
469
  {
470
    if ( first )
159✔
471
    {
472
      ChangesetTable chTable = schemaToChangesetTable( tableName, tbl );
90✔
473
      writer.beginTable( chTable );
90✔
474
      first = false;
90✔
475
    }
90✔
476

477
    ChangesetEntry e;
159✔
478
    e.op = reverse ? ChangesetEntry::OpDelete : ChangesetEntry::OpInsert;
159✔
479

480
    size_t numColumns = tbl.columns.size();
159✔
481
    for ( size_t i = 0; i < numColumns; ++i )
761✔
482
    {
483
      Sqlite3Value v( sqlite3_column_value( statementI.get(), static_cast<int>( i ) ) );
602✔
484
      if ( reverse )
602✔
485
        e.oldValues.push_back( changesetValue( v.value() ) );
76✔
486
      else
487
        e.newValues.push_back( changesetValue( v.value() ) );
526✔
488
    }
602✔
489

490
    writer.writeEntry( e );
159✔
491
  }
159✔
492
  if ( rc != SQLITE_DONE )
506✔
493
  {
494
    logSqliteError( context, db, "Failed to write information about inserted rows in table " + tableName );
×
495
  }
496
}
506✔
497

498
static void handleUpdated( const Context *context, const std::string &tableName, const TableSchema &tbl, std::shared_ptr<Sqlite3Db> db, ChangesetWriter &writer, bool &first )
253✔
499
{
500
  std::string sqlModified = sqlFindModified( tableName, tbl );
253✔
501

502
  Sqlite3Stmt statement;
253✔
503
  statement.prepare( db, "%s", sqlModified.c_str() );
253✔
504
  int rc;
505
  while ( SQLITE_ROW == ( rc = sqlite3_step( statement.get() ) ) )
298✔
506
  {
507
    /*
508
    ** Within the old.* record associated with an UPDATE change, all fields
509
    ** associated with table columns that are not PRIMARY KEY columns and are
510
    ** not modified by the UPDATE change are set to "undefined". Other fields
511
    ** are set to the values that made up the row before the UPDATE that the
512
    ** change records took place. Within the new.* record, fields associated
513
    ** with table columns modified by the UPDATE change contain the new
514
    ** values. Fields associated with table columns that are not modified
515
    ** are set to "undefined".
516
    */
517

518
    ChangesetEntry e;
45✔
519
    e.op = ChangesetEntry::OpUpdate;
45✔
520

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

552
        if ( updated )
53✔
553
        {
554
          hasUpdates = true;
51✔
555
        }
556
      }
557
      e.oldValues.push_back( ( pkey || updated ) ? changesetValue( v1.value() ) : Value() );
275✔
558
      e.newValues.push_back( updated ? changesetValue( v2.value() ) : Value() );
179✔
559
    }
179✔
560

561
    if ( hasUpdates )
45✔
562
    {
563
      if ( first )
43✔
564
      {
565
        ChangesetTable chTable = schemaToChangesetTable( tableName, tbl );
28✔
566
        writer.beginTable( chTable );
28✔
567
        first = false;
28✔
568
      }
28✔
569

570
      writer.writeEntry( e );
43✔
571
    }
572
  }
45✔
573
  if ( rc != SQLITE_DONE )
253✔
574
  {
575
    logSqliteError( context, db, "Failed to write information about inserted rows in table " + tableName );
×
576
  }
577
}
253✔
578

579
void SqliteDriver::createChangeset( ChangesetWriter &writer )
191✔
580
{
581
  std::vector<std::string> tablesBase = listTables( false );
191✔
582
  std::vector<std::string> tablesModified = listTables( true );
191✔
583

584
  if ( tablesBase != tablesModified )
191✔
585
  {
586
    throw GeoDiffException( "Table names are not matching between the input databases.\n"
3✔
587
                            "Base:     " + concatNames( tablesBase ) + "\n" +
6✔
588
                            "Modified: " + concatNames( tablesModified ) );
12✔
589
  }
590

591
  for ( const std::string &tableName : tablesBase )
441✔
592
  {
593
    TableSchema tbl = tableSchema( tableName );
259✔
594
    TableSchema tblNew = tableSchema( tableName, true );
259✔
595

596
    // test that table schema in the modified is the same
597
    if ( tbl != tblNew )
259✔
598
    {
599
      if ( !tbl.compareWithBaseTypes( tblNew ) )
18✔
600
        throw GeoDiffException( "GeoPackage Table schemas are not the same for table: " + tableName );
6✔
601
    }
602

603
    if ( !tbl.hasPrimaryKey() )
253✔
604
      continue;  // ignore tables without primary key - they can't be compared properly
×
605

606
    bool first = true;
253✔
607

608
    handleInserted( context(), tableName, tbl, false, mDb, writer, first );  // INSERT
253✔
609
    handleInserted( context(), tableName, tbl, true, mDb, writer, first );   // DELETE
253✔
610
    handleUpdated( context(), tableName, tbl, mDb, writer, first );          // UPDATE
253✔
611
  }
265✔
612

613
}
200✔
614

615
static std::string sqlForInsert( const std::string &tableName, const TableSchema &tbl )
88✔
616
{
617
  /*
618
   * For a table defined like this: CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
619
   *
620
   * INSERT INTO x (a, b, c, d) VALUES (?, ?, ?, ?)
621
   */
622

623
  std::string sql;
88✔
624
  sql += sqlitePrintf( "INSERT INTO \"%w\" (", tableName.c_str() );
88✔
625
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
415✔
626
  {
627
    if ( i > 0 )
327✔
628
      sql += ", ";
239✔
629
    sql += sqlitePrintf( "\"%w\"", tbl.columns[i].name.c_str() );
327✔
630
  }
631
  sql += ") VALUES (";
88✔
632
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
415✔
633
  {
634
    if ( i > 0 )
327✔
635
      sql += ", ";
239✔
636
    sql += "?";
327✔
637
  }
638
  sql += ")";
88✔
639
  return sql;
88✔
640
}
×
641

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

663
  std::string sql;
88✔
664
  sql += sqlitePrintf( "UPDATE \"%w\" SET ", tableName.c_str() );
88✔
665

666
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
415✔
667
  {
668
    if ( i > 0 )
327✔
669
      sql += ", ";
239✔
670
    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() );
327✔
671
  }
672
  sql += " WHERE ";
88✔
673
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
415✔
674
  {
675
    if ( i > 0 )
327✔
676
      sql += " AND ";
239✔
677
    if ( tbl.columns[i].isPrimaryKey )
327✔
678
      sql += sqlitePrintf( " \"%w\" = ?%d ", tbl.columns[i].name.c_str(), i * 3 + 1 );
88✔
679
    else if ( tbl.columns[i].type.baseType == TableColumnType::DATETIME )
239✔
680
    {
681
      // compare date/time values using datetime() because they may have
682
      // multiple equivalent string representations (see #143)
683
      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✔
684
    }
685
    else
686
      sql += sqlitePrintf( " ( ?%d = 0 OR \"%w\" IS ?%d ) ", i * 3 + 2, tbl.columns[i].name.c_str(), i * 3 + 1 );
235✔
687
  }
688

689
  return sql;
88✔
690
}
×
691

692
static std::string sqlForDelete( const std::string &tableName, const TableSchema &tbl )
88✔
693
{
694
  /*
695
   * For a table defined like this: CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
696
   *
697
   * DELETE FROM x WHERE a = ? AND b IS ? AND c = ? AND d IS ?
698
   */
699

700
  std::string sql;
88✔
701
  sql += sqlitePrintf( "DELETE FROM \"%w\" WHERE ", tableName.c_str() );
88✔
702
  for ( size_t i = 0; i < tbl.columns.size(); ++i )
415✔
703
  {
704
    if ( i > 0 )
327✔
705
      sql += " AND ";
239✔
706
    if ( tbl.columns[i].isPrimaryKey )
327✔
707
      sql += sqlitePrintf( "\"%w\" = ?", tbl.columns[i].name.c_str() );
88✔
708
    else if ( tbl.columns[i].type.baseType == TableColumnType::DATETIME )
239✔
709
    {
710
      // compare date/time values using strftime() because otherwise
711
      // fractional seconds will be lost
712
      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✔
713
    }
714
    else
715
      sql += sqlitePrintf( "\"%w\" IS ?", tbl.columns[i].name.c_str() );
235✔
716
  }
717
  return sql;
88✔
718
}
×
719

720
static void bindValue( sqlite3_stmt *stmt, int index, const Value &v )
770✔
721
{
722
  int rc;
723
  if ( v.type() == Value::TypeInt )
770✔
724
    rc = sqlite3_bind_int64( stmt, index, v.getInt() );
336✔
725
  else if ( v.type() == Value::TypeDouble )
434✔
726
    rc = sqlite3_bind_double( stmt, index, v.getDouble() );
11✔
727
  else if ( v.type() == Value::TypeNull )
423✔
728
    rc = sqlite3_bind_null( stmt, index );
73✔
729
  else if ( v.type() == Value::TypeText )
350✔
730
    rc = sqlite3_bind_text( stmt, index, v.getString().c_str(), -1, SQLITE_TRANSIENT );
187✔
731
  else if ( v.type() == Value::TypeBlob )
163✔
732
    rc = sqlite3_bind_blob( stmt, index, v.getString().c_str(), ( int ) v.getString().size(), SQLITE_TRANSIENT );
163✔
733
  else
734
    throw GeoDiffException( "unexpected bind type" );
×
735

736
  if ( rc != SQLITE_OK )
770✔
737
  {
738
    throw GeoDiffException( "bind failed" );
×
739
  }
740
}
770✔
741

742

743
void SqliteDriver::applyChangeset( ChangesetReader &reader )
74✔
744
{
745
  std::string lastTableName;
74✔
746
  std::unique_ptr<Sqlite3Stmt> stmtInsert, stmtUpdate, stmtDelete;
74✔
747
  TableSchema tbl;
74✔
748

749
  // this will acquire DB mutex and release it when the function ends (or when an exception is thrown)
750
  Sqlite3DbMutexLocker dbMutexLocker( mDb );
74✔
751

752
  // start transaction!
753
  Sqlite3SavepointTransaction savepointTransaction( context(), mDb );
74✔
754

755
  // TODO: when we deal with foreign keys, it may be useful to temporarily set "PRAGMA defer_foreign_keys = 1"
756
  // so that if a foreign key is violated, the constraint violation is tolerated until we commit the changes
757
  // (and at that point the violation may have been fixed by some other commands). Sqlite3session does that.
758

759
  // get all triggers sql commands
760
  // that we do not recognize (gpkg triggers are filtered)
761
  std::vector<std::string> triggerNames;
74✔
762
  std::vector<std::string> triggerCmds;
74✔
763
  sqliteTriggers( context(), mDb, triggerNames, triggerCmds );
74✔
764

765
  Sqlite3Stmt statament;
74✔
766
  for ( std::string name : triggerNames )
75✔
767
  {
768
    statament.prepare( mDb, "drop trigger '%q'", name.c_str() );
1✔
769
    int rc = sqlite3_step( statament.get() );
1✔
770
    if ( SQLITE_DONE != rc )
1✔
771
    {
772
      logSqliteError( context(), mDb, "Failed to drop trigger " + name );
×
773
    }
774
    statament.close();
1✔
775
  }
1✔
776

777
  int conflictCount = 0;
74✔
778
  ChangesetEntry entry;
74✔
779
  while ( reader.nextEntry( entry ) )
285✔
780
  {
781
    std::string tableName = entry.table->name;
211✔
782

783
    if ( startsWith( tableName, "gpkg_" ) )
211✔
784
      continue;   // skip any changes to GPKG meta tables
4✔
785

786
    // skip table if necessary
787
    if ( context()->isTableSkipped( tableName ) )
207✔
788
    {
789
      continue;
6✔
790
    }
791

792
    if ( tableName != lastTableName )
201✔
793
    {
794
      lastTableName = tableName;
88✔
795
      tbl = tableSchema( tableName );
88✔
796

797
      if ( tbl.columns.size() == 0 )
88✔
798
        throw GeoDiffException( "No such table: " + tableName );
×
799

800
      if ( tbl.columns.size() != entry.table->columnCount() )
88✔
801
        throw GeoDiffException( "Wrong number of columns for table: " + tableName );
×
802

803
      for ( size_t i = 0; i < entry.table->columnCount(); ++i )
415✔
804
      {
805
        if ( tbl.columns[i].isPrimaryKey != entry.table->primaryKeys[i] )
327✔
806
          throw GeoDiffException( "Mismatch of primary keys in table: " + tableName );
×
807
      }
808

809
      stmtInsert.reset( new Sqlite3Stmt );
88✔
810
      stmtInsert->prepare( mDb, sqlForInsert( tableName, tbl ) );
88✔
811

812
      stmtUpdate.reset( new Sqlite3Stmt );
88✔
813
      stmtUpdate->prepare( mDb, sqlForUpdate( tableName, tbl ) );
88✔
814

815
      stmtDelete.reset( new Sqlite3Stmt );
88✔
816
      stmtDelete->prepare( mDb, sqlForDelete( tableName, tbl ) );
88✔
817
    }
818

819
    if ( entry.op == SQLITE_INSERT )
201✔
820
    {
821
      for ( size_t i = 0; i < tbl.columns.size(); ++i )
517✔
822
      {
823
        const Value &v = entry.newValues[i];
410✔
824
        bindValue( stmtInsert->get(), static_cast<int>( i ) + 1, v );
410✔
825
      }
826
      if ( sqlite3_step( stmtInsert->get() ) != SQLITE_DONE )
107✔
827
      {
828
        // it could be that the primary key already exists or some constraint violation (e.g. not null, unique)
829
        logApplyConflict( "insert_failed", entry );
×
830
        ++conflictCount;
×
831
      }
832
      if ( sqlite3_changes( mDb->get() ) != 1 )
107✔
833
        throw GeoDiffException( "Nothing inserted (this should never happen)" );
×
834
      sqlite3_reset( stmtInsert->get() );
107✔
835
    }
836
    else if ( entry.op == SQLITE_UPDATE )
94✔
837
    {
838
      for ( size_t i = 0; i < tbl.columns.size(); ++i )
252✔
839
      {
840
        const Value &vOld = entry.oldValues[i];
200✔
841
        const Value &vNew = entry.newValues[i];
200✔
842
        sqlite3_bind_int( stmtUpdate->get(), static_cast<int>( i ) * 3 + 2, vNew.type() != Value::TypeUndefined );
200✔
843
        if ( vOld.type() != Value::TypeUndefined )
200✔
844
          bindValue( stmtUpdate->get(), static_cast<int>( i ) * 3 + 1, vOld );
124✔
845
        if ( vNew.type() != Value::TypeUndefined )
200✔
846
          bindValue( stmtUpdate->get(), static_cast<int>( i ) * 3 + 3, vNew );
72✔
847
      }
848
      if ( sqlite3_step( stmtUpdate->get() ) != SQLITE_DONE )
52✔
849
      {
850
        // a constraint must have been violated (e.g. foreign key, not null, unique)
851
        logApplyConflict( "update_failed", entry );
×
852
        ++conflictCount;
×
853
      }
854
      if ( sqlite3_changes( mDb->get() ) == 0 )
52✔
855
      {
856
        // either the row with such pkey does not exist or its data have been modified
857
        logApplyConflict( "update_nothing", entry );
2✔
858
        ++conflictCount;
2✔
859
      }
860
      sqlite3_reset( stmtUpdate->get() );
52✔
861
    }
862
    else if ( entry.op == SQLITE_DELETE )
42✔
863
    {
864
      for ( size_t i = 0; i < tbl.columns.size(); ++i )
206✔
865
      {
866
        const Value &v = entry.oldValues[i];
164✔
867
        bindValue( stmtDelete->get(), static_cast<int>( i ) + 1, v );
164✔
868
      }
869
      if ( sqlite3_step( stmtDelete->get() ) != SQLITE_DONE )
42✔
870
      {
871
        // a foreign key constraint must have been violated
872
        logApplyConflict( "delete_failed", entry );
×
873
        ++conflictCount;
×
874
      }
875
      if ( sqlite3_changes( mDb->get() ) == 0 )
42✔
876
      {
877
        // either the row with such pkey does not exist or its data have been modified
878
        logApplyConflict( "delete_nothing", entry );
1✔
879
        ++conflictCount;
1✔
880
      }
881
      sqlite3_reset( stmtDelete->get() );
42✔
882
    }
883
    else
884
      throw GeoDiffException( "Unexpected operation" );
×
885
  }
211✔
886

887
  // recreate triggers
888
  for ( std::string cmd : triggerCmds )
75✔
889
  {
890
    statament.prepare( mDb, "%s", cmd.c_str() );
1✔
891
    if ( SQLITE_DONE != sqlite3_step( statament.get() ) )
1✔
892
    {
893
      logSqliteError( context(), mDb, "Failed to recreate trigger using SQL \"" + cmd + "\"" );
×
894
    }
895
    statament.close();
1✔
896
  }
1✔
897

898
  if ( !conflictCount )
74✔
899
  {
900
    savepointTransaction.commitChanges();
71✔
901
  }
902
  else
903
  {
904
    throw GeoDiffException( "Conflicts encountered while applying changes! Total " + std::to_string( conflictCount ) );
3✔
905
  }
906
}
104✔
907

908

909
static void addGpkgCrsDefinition( std::shared_ptr<Sqlite3Db> db, const CrsDefinition &crs )
21✔
910
{
911
  // gpkg_spatial_ref_sys
912
  //   srs_name TEXT NOT NULL, srs_id INTEGER NOT NULL PRIMARY KEY,
913
  //   organization TEXT NOT NULL, organization_coordsys_id INTEGER NOT NULL,
914
  //   definition  TEXT NOT NULL, description TEXT
915

916
  Sqlite3Stmt stmtCheck;
21✔
917
  stmtCheck.prepare( db, "select count(*) from gpkg_spatial_ref_sys where srs_id = %d;", crs.srsId );
21✔
918
  int res = sqlite3_step( stmtCheck.get() );
21✔
919
  if ( res != SQLITE_ROW )
21✔
920
  {
921
    throwSqliteError( db->get(), "Failed to access gpkg_spatial_ref_sys table" );
×
922
  }
923

924
  if ( sqlite3_column_int( stmtCheck.get(), 0 ) )
21✔
925
    return;  // already there
21✔
926

927
  Sqlite3Stmt stmt;
×
928
  stmt.prepare( db, "INSERT INTO gpkg_spatial_ref_sys VALUES ('%q:%d', %d, '%q', %d, '%q', '')",
×
929
                crs.authName.c_str(), crs.authCode, crs.srsId, crs.authName.c_str(), crs.authCode,
×
930
                crs.wkt.c_str() );
931
  res = sqlite3_step( stmt.get() );
×
932
  if ( res != SQLITE_DONE )
×
933
  {
934
    throwSqliteError( db->get(), "Failed to insert CRS to gpkg_spatial_ref_sys table" );
×
935
  }
936
}
21✔
937

938
static void addGpkgSpatialTable( std::shared_ptr<Sqlite3Db> db, const TableSchema &tbl, const Extent &extent )
21✔
939
{
940
  size_t i = tbl.geometryColumn();
21✔
941
  if ( i == SIZE_MAX )
21✔
942
    throw GeoDiffException( "Adding non-spatial tables is not supported: " + tbl.name );
×
943

944
  const TableColumnInfo &col = tbl.columns[i];
21✔
945
  std::string geomColumn = col.name;
21✔
946
  std::string geomType = col.geomType;
21✔
947
  int srsId = col.geomSrsId;
21✔
948
  bool hasZ = col.geomHasZ;
21✔
949
  bool hasM = col.geomHasM;
21✔
950

951
  // gpkg_contents
952
  //   table_name TEXT NOT NULL PRIMARY KEY, data_type TEXT NOT NULL,
953
  //   identifier TEXT, description TEXT DEFAULT '',
954
  //   last_change DATETIME NOT NULL DEFAULT (...),
955
  //   min_x DOUBLE, min_y DOUBLE, max_x DOUBLE, max_y DOUBLE,
956
  //   srs_id INTEGER
957

958
  Sqlite3Stmt stmt;
21✔
959
  stmt.prepare( db, "INSERT INTO gpkg_contents (table_name, data_type, identifier, min_x, min_y, max_x, max_y, srs_id) "
21✔
960
                "VALUES ('%q', 'features', '%q', %f, %f, %f, %f, %d)",
961
                tbl.name.c_str(), tbl.name.c_str(), extent.minX, extent.minY, extent.maxX, extent.maxY, srsId );
21✔
962
  int res = sqlite3_step( stmt.get() );
21✔
963
  if ( res != SQLITE_DONE )
21✔
964
  {
965
    throwSqliteError( db->get(), "Failed to insert row to gpkg_contents table" );
×
966
  }
967

968
  // gpkg_geometry_columns
969
  //   table_name TEXT NOT NULL, column_name TEXT NOT NULL,
970
  //   geometry_type_name TEXT NOT NULL, srs_id INTEGER NOT NULL,
971
  //   z TINYINT NOT NULL,m TINYINT NOT NULL
972

973
  Sqlite3Stmt stmtGeomCol;
21✔
974
  stmtGeomCol.prepare( db, "INSERT INTO gpkg_geometry_columns VALUES ('%q', '%q', '%q', %d, %d, %d)",
21✔
975
                       tbl.name.c_str(), geomColumn.c_str(), geomType.c_str(), srsId, hasZ, hasM );
976
  res = sqlite3_step( stmtGeomCol.get() );
21✔
977
  if ( res != SQLITE_DONE )
21✔
978
  {
979
    throwSqliteError( db->get(), "Failed to insert row to gpkg_geometry_columns table" );
×
980
  }
981
}
21✔
982

983
void SqliteDriver::createTables( const std::vector<TableSchema> &tables )
13✔
984
{
985
  // currently we always create geopackage meta tables. Maybe in the future we can skip
986
  // that if there is a reason, and have that optional if none of the tables are spatial.
987
  Sqlite3Stmt stmt1;
13✔
988
  stmt1.prepare( mDb, "SELECT InitSpatialMetadata('main');" );
13✔
989
  int res = sqlite3_step( stmt1.get() );
13✔
990
  if ( res != SQLITE_ROW )
13✔
991
  {
992
    throwSqliteError( mDb->get(), "Failure initializing spatial metadata" );
×
993
  }
994

995
  for ( const TableSchema &tbl : tables )
34✔
996
  {
997
    if ( startsWith( tbl.name, "gpkg_" ) )
21✔
998
      continue;
×
999

1000
    if ( tbl.geometryColumn() != SIZE_MAX )
21✔
1001
    {
1002
      addGpkgCrsDefinition( mDb, tbl.crs );
21✔
1003
      addGpkgSpatialTable( mDb, tbl, Extent() );   // TODO: is it OK to set zeros?
21✔
1004
    }
1005

1006
    std::string sql, pkeyCols, columns;
21✔
1007
    for ( const TableColumnInfo &c : tbl.columns )
95✔
1008
    {
1009
      if ( !columns.empty() )
74✔
1010
        columns += ", ";
53✔
1011

1012
      columns += sqlitePrintf( "\"%w\" %s", c.name.c_str(), c.type.dbType.c_str() );
74✔
1013

1014
      if ( c.isNotNull )
74✔
1015
        columns += " NOT NULL";
21✔
1016

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

1023
      if ( c.isPrimaryKey )
74✔
1024
      {
1025
        if ( !pkeyCols.empty() )
21✔
1026
          pkeyCols += ", ";
×
1027
        pkeyCols += sqlitePrintf( "\"%w\"", c.name.c_str() );
21✔
1028
      }
1029
    }
1030

1031
    sql = sqlitePrintf( "CREATE TABLE \"%w\".\"%w\" (", "main", tbl.name.c_str() );
21✔
1032
    sql += columns;
21✔
1033
    sql += ", PRIMARY KEY (" + pkeyCols + ")";
21✔
1034
    sql += ");";
21✔
1035

1036
    Sqlite3Stmt stmt;
21✔
1037
    stmt.prepare( mDb, sql );
21✔
1038
    if ( sqlite3_step( stmt.get() ) != SQLITE_DONE )
21✔
1039
    {
1040
      throwSqliteError( mDb->get(), "Failure creating table: " + tbl.name );
×
1041
    }
1042
  }
21✔
1043
}
13✔
1044

1045

1046
void SqliteDriver::dumpData( ChangesetWriter &writer, bool useModified )
12✔
1047
{
1048
  std::string dbName = databaseName( useModified );
12✔
1049
  std::vector<std::string> tables = listTables();
12✔
1050
  for ( const std::string &tableName : tables )
40✔
1051
  {
1052
    TableSchema tbl = tableSchema( tableName, useModified );
28✔
1053
    if ( !tbl.hasPrimaryKey() )
28✔
1054
      continue;  // ignore tables without primary key - they can't be compared properly
×
1055

1056
    bool first = true;
28✔
1057
    Sqlite3Stmt statementI;
28✔
1058
    statementI.prepare( mDb, "SELECT * FROM \"%w\".\"%w\"", dbName.c_str(), tableName.c_str() );
28✔
1059
    int rc;
1060
    while ( SQLITE_ROW == ( rc = sqlite3_step( statementI.get() ) ) )
86✔
1061
    {
1062
      if ( first )
58✔
1063
      {
1064
        writer.beginTable( schemaToChangesetTable( tableName, tbl ) );
27✔
1065
        first = false;
27✔
1066
      }
1067

1068
      ChangesetEntry e;
58✔
1069
      e.op = ChangesetEntry::OpInsert;
58✔
1070
      size_t numColumns = tbl.columns.size();
58✔
1071
      for ( size_t i = 0; i < numColumns; ++i )
281✔
1072
      {
1073
        Sqlite3Value v( sqlite3_column_value( statementI.get(), static_cast<int>( i ) ) );
223✔
1074
        e.newValues.push_back( changesetValue( v.value() ) );
223✔
1075
      }
223✔
1076
      writer.writeEntry( e );
58✔
1077
    }
58✔
1078
    if ( rc != SQLITE_DONE )
28✔
1079
    {
1080
      logSqliteError( context(), mDb, "Failure dumping changeset" );
×
1081
    }
1082
  }
28✔
1083
}
12✔
1084

1085
void SqliteDriver::checkCompatibleForRebase( bool useModified )
18✔
1086
{
1087
  std::string dbName = databaseName( useModified );
18✔
1088

1089
  // get all triggers sql commands
1090
  // and make sure that there are only triggers we recognize
1091
  // we deny rebase changesets with unrecognized triggers
1092
  std::vector<std::string> triggerNames;
18✔
1093
  std::vector<std::string> triggerCmds;
18✔
1094
  sqliteTriggers( context(), mDb, triggerNames, triggerCmds );  // TODO: use dbName
18✔
1095
  if ( !triggerNames.empty() )
18✔
1096
  {
1097
    std::string msg = "Unable to perform rebase for database with unknown triggers:\n";
×
1098
    for ( size_t i = 0; i < triggerNames.size(); ++i )
×
1099
      msg += triggerNames[i] + "\n";
×
1100
    throw GeoDiffException( msg );
×
1101
  }
×
1102

1103
  ForeignKeys fks = sqliteForeignKeys( context(), mDb, dbName );
18✔
1104
  if ( !fks.empty() )
18✔
1105
  {
1106
    throw GeoDiffException( "Unable to perform rebase for database with foreign keys" );
4✔
1107
  }
1108
}
30✔
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