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

MerginMaps / geodiff / 20364107471

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

push

github

wonder-sk
Improve Postgres constraint error handling

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

148 existing lines in 5 files now uncovered.

3637 of 4130 relevant lines covered (88.06%)

573.59 hits per line

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

62.26
/geodiff/src/drivers/sqliteutils.cpp
1
/*
2
 GEODIFF - MIT License
3
 Copyright (C) 2020 Peter Petrik, Martin Dobias
4
*/
5

6
#include "sqliteutils.h"
7

8
#include "geodiffutils.hpp"
9
#include "geodifflogger.hpp"
10
#include "geodiffcontext.hpp"
11

12
#include <gpkg.h>
13

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

17
extern "C" {
18
#include "binstream.h"
19
#include "wkb.h"
20
#include "gpkg_geom.h"
21
}
22

23
Sqlite3Db::Sqlite3Db() = default;
451✔
24
Sqlite3Db::~Sqlite3Db()
451✔
25
{
26
  close();
451✔
27
}
451✔
28

29
void Sqlite3Db::open( const std::string &filename )
419✔
30
{
31
  close();
419✔
32
  int rc = sqlite3_open_v2( filename.c_str(), &mDb, SQLITE_OPEN_READWRITE, nullptr );
419✔
33
  if ( rc )
419✔
34
  {
35
    throwSqliteError( mDb, "Unable to open " + filename + " as sqlite3 database" );
×
36
  }
37
}
419✔
38

39
void Sqlite3Db::create( const std::string &filename )
30✔
40
{
41
  close();
30✔
42

43
  if ( fileexists( filename ) )
30✔
44
  {
45
    throw GeoDiffException( "Unable to create sqlite3 database - already exists: " + filename );
×
46
  }
47

48
  int rc = sqlite3_open_v2( filename.c_str(), &mDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, nullptr );
30✔
49
  if ( rc )
30✔
50
  {
51
    throwSqliteError( mDb, "Unable to create " + filename + " as sqlite3 database" );
×
52
  }
53
}
30✔
54

55
void Sqlite3Db::exec( const Buffer &buf )
662✔
56
{
57
  int rc = sqlite3_exec( get(), buf.c_buf(), NULL, 0, NULL );
662✔
58
  if ( rc )
662✔
59
  {
60
    throwSqliteError( get(), "Unable to exec buffer on sqlite3 database" );
2✔
61
  }
62
}
661✔
63

64
sqlite3 *Sqlite3Db::get()
15,828✔
65
{
66
  return mDb;
15,828✔
67
}
68

69
void Sqlite3Db::close()
900✔
70
{
71
  if ( mDb )
900✔
72
  {
73
    sqlite3_close( mDb );
449✔
74
    mDb = nullptr;
449✔
75
  }
76
}
900✔
77

78

79
///
80

81

82
Sqlite3Stmt::Sqlite3Stmt() = default;
7,051✔
83

84
Sqlite3Stmt::~Sqlite3Stmt()
7,051✔
85
{
86
  close();
7,051✔
87
}
7,051✔
88

89
static sqlite3_stmt *db_vprepare( sqlite3 *db, const char *zFormat, va_list ap )
6,657✔
90
{
91
  char *zSql;
92
  int rc;
93
  sqlite3_stmt *pStmt;
94

95
  zSql = sqlite3_vmprintf( zFormat, ap );
6,657✔
96
  if ( zSql == nullptr )
6,657✔
97
  {
98
    throw GeoDiffException( "out of memory" );
×
99
  }
100

101
  rc = sqlite3_prepare_v2( db, zSql, -1, &pStmt, nullptr );
6,657✔
102
  sqlite3_free( zSql );
6,657✔
103
  if ( rc )
6,657✔
104
  {
105
    throwSqliteError( db, "Unable to prepare SQL statement in db_vprepare() call" );
×
106
  }
107
  return pStmt;
6,657✔
108
}
109

110
void Sqlite3Stmt::prepare( std::shared_ptr<Sqlite3Db> db, const char *zFormat, ... )
6,657✔
111
{
112
  if ( db && db->get() )
6,657✔
113
  {
114
    va_list ap;
115
    va_start( ap, zFormat );
6,657✔
116
    mStmt = db_vprepare( db->get(), zFormat, ap );
6,657✔
117
    va_end( ap );
6,657✔
118
  }
119
}
6,657✔
120

121
void Sqlite3Stmt::prepare( std::shared_ptr<Sqlite3Db> db, const std::string &sql )
398✔
122
{
123
  sqlite3_stmt *pStmt;
124
  int rc = sqlite3_prepare_v2( db->get(), sql.c_str(), -1, &pStmt, nullptr );
398✔
125
  if ( rc )
398✔
126
  {
127
    throwSqliteError( db->get(), "Unable to prepare SQL statement in prepare() call" );
×
128
  }
129
  mStmt = pStmt;
398✔
130
}
398✔
131

132
sqlite3_stmt *Sqlite3Stmt::get()
60,918✔
133
{
134
  return mStmt;
60,918✔
135
}
136

137
void Sqlite3Stmt::close()
7,652✔
138
{
139
  if ( mStmt )
7,652✔
140
  {
141
    sqlite3_finalize( mStmt );
7,055✔
142
    mStmt = nullptr;
7,055✔
143
  }
144
}
7,652✔
145

146
///
147

148
Sqlite3Value::Sqlite3Value( const sqlite3_value *ppValue )
1,815✔
149
{
150
  if ( ppValue )
1,815✔
151
  {
152
    mVal = sqlite3_value_dup( ppValue );
1,815✔
153
  }
154
}
1,815✔
155

156
Sqlite3Value::~Sqlite3Value()
1,815✔
157
{
158
  if ( mVal )
1,815✔
159
  {
160
    sqlite3_value_free( mVal );
1,815✔
161
  }
162
}
1,815✔
163

164
sqlite3_value *Sqlite3Value::value() const
1,507✔
165
{
166
  return mVal;
1,507✔
167
}
168

169
/*
170
std::string toString( sqlite3_value *ppValue )
171
{
172
  if ( !ppValue )
173
    return "nil";
174
  std::string val = "n/a";
175
  int type = sqlite3_value_type( ppValue );
176
  if ( type == SQLITE_INTEGER )
177
    val = std::to_string( sqlite3_value_int( ppValue ) );
178
  else if ( type == SQLITE_TEXT )
179
    val = std::string( reinterpret_cast<const char *>( sqlite3_value_text( ppValue ) ) );
180
  else if ( type == SQLITE_FLOAT )
181
    val = std::to_string( sqlite3_value_double( ppValue ) );
182
  else if ( type == SQLITE_BLOB )
183
    val = "blob " + std::to_string( sqlite3_value_bytes( ppValue ) ) + " bytes";
184
  return val;
185
}
186
*/
187

188
bool Sqlite3Value::operator==( const Sqlite3Value &other ) const
283✔
189
{
190
  sqlite3_value *v1 = mVal;
283✔
191
  sqlite3_value *v2 = other.mVal;
283✔
192

193
  int type1 = sqlite3_value_type( v1 );
283✔
194
  int type2 = sqlite3_value_type( v2 );
283✔
195
  if ( type1 != type2 )
283✔
196
    return false;
7✔
197

198
  if ( type1 == SQLITE_NULL )
276✔
199
    return true;
11✔
200
  else if ( type1 == SQLITE_INTEGER )
265✔
201
    return sqlite3_value_int64( v1 ) == sqlite3_value_int64( v2 );
133✔
202
  else if ( type1 == SQLITE_FLOAT )
132✔
203
    return sqlite3_value_double( v1 ) == sqlite3_value_double( v2 );
3✔
204
  else if ( type1 == SQLITE_TEXT )
129✔
205
  {
206
    return strcmp(
81✔
207
             reinterpret_cast<const char *>( sqlite3_value_text( v1 ) ),
162✔
208
             reinterpret_cast<const char *>( sqlite3_value_text( v2 ) ) ) == 0;
81✔
209
  }
210
  else if ( type1 == SQLITE_BLOB )
48✔
211
  {
212
    int len1 = sqlite3_value_bytes( v1 );
48✔
213
    int len2 = sqlite3_value_bytes( v2 );
48✔
214
    if ( len1 != len2 )
48✔
215
      return false;
×
216
    return memcmp( sqlite3_value_blob( v1 ), sqlite3_value_blob( v2 ), len1 ) == 0;
48✔
217
  }
218
  else
219
  {
220
    throw GeoDiffException( "Unexpected value type" );
×
221
  }
222
}
223

224

225
///
226

227

228
void register_gpkg_extensions( std::shared_ptr<Sqlite3Db> db )
350✔
229
{
230
  // register GPKG functions like ST_IsEmpty
231
  int rc = sqlite3_enable_load_extension( db->get(), 1 );
350✔
232
  if ( rc )
350✔
233
  {
234
    throwSqliteError( db->get(), "Failed to enable SQLite extensions loading" );
×
235
  }
236

237
  rc = sqlite3_gpkg_auto_init( db->get(), NULL, NULL );
350✔
238
  if ( rc )
350✔
239
  {
240
    throwSqliteError( db->get(), "Failed to initialize GPKG extension" );
×
241
  }
242
}
350✔
243

244

245
bool isGeoPackage( const Context *context, std::shared_ptr<Sqlite3Db> db )
399✔
246
{
247
  std::vector<std::string> tableNames;
399✔
248
  sqliteTables( context,
399✔
249
                db,
250
                "main",
251
                tableNames );
252

253
  return std::find( tableNames.begin(), tableNames.end(), "gpkg_contents" ) != tableNames.end();
798✔
254
}
399✔
255

256

257
void sqliteTriggers( const Context *context, std::shared_ptr<Sqlite3Db> db, std::vector<std::string> &triggerNames, std::vector<std::string> &triggerCmds )
99✔
258
{
259
  triggerNames.clear();
99✔
260
  triggerCmds.clear();
99✔
261

262
  Sqlite3Stmt statament;
99✔
263
  statament.prepare( db, "%s", "select name, sql from sqlite_master where type = 'trigger'" );
99✔
264
  int rc;
265
  while ( SQLITE_ROW == ( rc = sqlite3_step( statament.get() ) ) )
1,533✔
266
  {
267
    const char *name = reinterpret_cast<const char *>( sqlite3_column_text( statament.get(), 0 ) );
1,434✔
268
    const char *sql = reinterpret_cast<const char *>( sqlite3_column_text( statament.get(), 1 ) );
1,434✔
269

270
    if ( !name || !sql )
1,434✔
271
      continue;
1,432✔
272

273
    /* typically geopackage from ogr would have these (table name is simple)
274
        - gpkg_tile_matrix_zoom_level_insert
275
        - gpkg_tile_matrix_zoom_level_update
276
        - gpkg_tile_matrix_matrix_width_insert
277
        - gpkg_tile_matrix_matrix_width_update
278
        - gpkg_tile_matrix_matrix_height_insert
279
        - gpkg_tile_matrix_matrix_height_update
280
        - gpkg_tile_matrix_pixel_x_size_insert
281
        - gpkg_tile_matrix_pixel_x_size_update
282
        - gpkg_tile_matrix_pixel_y_size_insert
283
        - gpkg_tile_matrix_pixel_y_size_update
284
        - gpkg_metadata_md_scope_insert
285
        - gpkg_metadata_md_scope_update
286
        - gpkg_metadata_reference_reference_scope_insert
287
        - gpkg_metadata_reference_reference_scope_update
288
        - gpkg_metadata_reference_column_name_insert
289
        - gpkg_metadata_reference_column_name_update
290
        - gpkg_metadata_reference_row_id_value_insert
291
        - gpkg_metadata_reference_row_id_value_update
292
        - gpkg_metadata_reference_timestamp_insert
293
        - gpkg_metadata_reference_timestamp_update
294
        - rtree_simple_geometry_insert
295
        - rtree_simple_geometry_update1
296
        - rtree_simple_geometry_update2
297
        - rtree_simple_geometry_update3
298
        - rtree_simple_geometry_update4
299
        - rtree_simple_geometry_delete
300
        - trigger_insert_feature_count_simple
301
        - trigger_delete_feature_count_simple
302
     */
303
    const std::string triggerName( name );
2,868✔
304
    if ( startsWith( triggerName, "gpkg_" ) )
2,868✔
305
      continue;
720✔
306
    if ( startsWith( triggerName, "rtree_" ) )
1,428✔
307
      continue;
522✔
308
    if ( startsWith( triggerName, "trigger_insert_feature_count_" ) )
384✔
309
      continue;
95✔
310
    if ( startsWith( triggerName, "trigger_delete_feature_count_" ) )
194✔
311
      continue;
95✔
312
    triggerNames.push_back( name );
4✔
313
    triggerCmds.push_back( sql );
2✔
314
  }
1,434✔
315
  if ( rc != SQLITE_DONE )
99✔
316
  {
317
    logSqliteError( context, db, "Failed to get list of triggers" );
×
318
  }
319
  statament.close();
99✔
320
}
99✔
321

322

323
ForeignKeys sqliteForeignKeys( const Context *context, std::shared_ptr<Sqlite3Db> db, const std::string &dbName )
×
324
{
325
  std::vector<std::string> fromTableNames;
×
326
  sqliteTables( context, db, dbName, fromTableNames );
×
327

328
  ForeignKeys ret;
×
329

330
  for ( const std::string &fromTableName : fromTableNames )
×
331
  {
332
    if ( isLayerTable( fromTableName ) )
×
333
    {
334
      Sqlite3Stmt pStmt;     /* SQL statement being run */
×
335
      pStmt.prepare( db, "SELECT * FROM %s.pragma_foreign_key_list(%Q)", dbName.c_str(), fromTableName.c_str() );
×
336
      int rc;
337
      while ( SQLITE_ROW == ( rc = sqlite3_step( pStmt.get() ) ) )
×
338
      {
339
        const char *fk_to_table = reinterpret_cast<const char *>( sqlite3_column_text( pStmt.get(), 2 ) );
×
340
        const char *fk_from = reinterpret_cast<const char *>( sqlite3_column_text( pStmt.get(), 3 ) );
×
341
        const char *fk_to = reinterpret_cast<const char *>( sqlite3_column_text( pStmt.get(), 4 ) );
×
342

343
        if ( fk_to_table && fk_from && fk_to )
×
344
        {
345
          // TODO: this part is not speed-optimized and could be slower for databases with a lot of
346
          // columns and/or foreign keys. For each entry we grab column names again
347
          // and we search for index of value in plain std::vector array...
348
          std::vector<std::string> fromColumnNames = sqliteColumnNames( context, db, dbName, fromTableName );
×
349
          int fk_from_id = indexOf( fromColumnNames, fk_from );
×
350
          if ( fk_from_id < 0 )
×
351
            continue;
×
352

353
          std::vector<std::string> toColumnNames = sqliteColumnNames( context, db, dbName, fk_to_table );
×
354
          int fk_to_id = indexOf( toColumnNames, fk_to );
×
355
          if ( fk_to_id < 0 )
×
356
            continue;
×
357

358
          TableColumn from( fromTableName, fk_from_id );
×
359
          TableColumn to( fk_to_table, fk_to_id );
×
360
          ret.insert( std::pair<TableColumn, TableColumn>( from, to ) );
×
361
        }
×
362
      }
363
      if ( rc != SQLITE_DONE )
×
364
      {
365
        logSqliteError( context, db, "Failed to get list of foreing keys" );
×
366
      }
367
      pStmt.close();
×
368
    }
×
369
  }
370

371
  return ret;
×
372
}
×
373

374

375
void sqliteTables( const Context *context,
399✔
376
                   std::shared_ptr<Sqlite3Db> db,
377
                   const std::string &dbName,
378
                   std::vector<std::string> &tableNames )
379
{
380
  tableNames.clear();
399✔
381
  std::string all_tables_sql = "SELECT name FROM " + dbName + ".sqlite_master\n"
798✔
382
                               " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
383
                               " ORDER BY name";
399✔
384
  Sqlite3Stmt statament;
399✔
385
  statament.prepare( db, "%s", all_tables_sql.c_str() );
399✔
386
  int rc;
387
  while ( SQLITE_ROW == ( rc = sqlite3_step( statament.get() ) ) )
5,004✔
388
  {
389
    const char *name = reinterpret_cast<const char *>( sqlite3_column_text( statament.get(), 0 ) );
4,605✔
390
    if ( !name )
4,605✔
391
      continue;
1,933✔
392

393
    std::string tableName( name );
9,210✔
394
    /* typically geopackage from ogr would have these (table name is simple)
395
    gpkg_contents
396
    gpkg_extensions
397
    gpkg_geometry_columns
398
    gpkg_ogr_contents
399
    gpkg_spatial_ref_sys
400
    gpkg_tile_matrix
401
    gpkg_tile_matrix_set
402
    rtree_simple_geometry_node
403
    rtree_simple_geometry_parent
404
    rtree_simple_geometry_rowid
405
    simple (or any other name(s) of layers)
406
    sqlite_sequence
407
    */
408

409
    // table handled by triggers trigger_*_feature_count_*
410
    if ( startsWith( tableName, "gpkg_ogr_contents" ) )
9,210✔
411
      continue;
328✔
412
    // table handled by triggers rtree_*_geometry_*
413
    if ( startsWith( tableName, "rtree_" ) )
8,554✔
414
      continue;
1,269✔
415
    // internal table for AUTOINCREMENT
416
    if ( tableName == "sqlite_sequence" )
3,008✔
417
      continue;
336✔
418

419
    tableNames.push_back( tableName );
2,672✔
420
  }
4,605✔
421
  if ( rc != SQLITE_DONE )
399✔
422
  {
423
    logSqliteError( context, db, "Failed to get list of tables" );
×
424
  }
425
  statament.close();
399✔
426
  // result is ordered by name
427
}
399✔
428

429

430
/*
431
 * inspired by sqldiff.c function: columnNames()
432
 */
433
std::vector<std::string> sqliteColumnNames(
×
434
  const Context *context,
435
  std::shared_ptr<Sqlite3Db> db,
436
  const std::string &zDb,                /* Database ("main" or "aux") to query */
437
  const std::string &tableName   /* Name of table to return details of */
438
)
439
{
440
  std::vector<std::string> az;           /* List of column names to be returned */
×
441
  size_t naz = 0;             /* Number of entries in az[] */
×
442
  Sqlite3Stmt pStmt;     /* SQL statement being run */
×
443
  std::string zPkIdxName;    /* Name of the PRIMARY KEY index */
×
444
  int truePk = 0;          /* PRAGMA table_info indentifies the PK to use */
×
445
  int nPK = 0;             /* Number of PRIMARY KEY columns */
×
446

447
  /* Figure out what the true primary key is for the table.
448
  **   *  For WITHOUT ROWID tables, the true primary key is the same as
449
  **      the schema PRIMARY KEY, which is guaranteed to be present.
450
  **   *  For rowid tables with an INTEGER PRIMARY KEY, the true primary
451
  **      key is the INTEGER PRIMARY KEY.
452
  **   *  For all other rowid tables, the rowid is the true primary key.
453
  */
454
  const char *zTab = tableName.c_str();
×
455
  pStmt.prepare( db, "PRAGMA %s.index_list=%Q", zDb.c_str(), zTab );
×
456
  int rc;
457
  while ( SQLITE_ROW == ( rc = sqlite3_step( pStmt.get() ) ) )
×
458
  {
459
    if ( sqlite3_stricmp( reinterpret_cast<const char *>( sqlite3_column_text( pStmt.get(), 3 ) ), "pk" ) == 0 )
×
460
    {
461
      zPkIdxName = reinterpret_cast<const char *>( sqlite3_column_text( pStmt.get(), 1 ) );
×
462
      break;
×
463
    }
464
  }
465
  if ( rc != SQLITE_DONE )
×
466
  {
467
    logSqliteError( context, db, "Failed to get list of primary keys for table " + tableName );
×
468
  }
469
  pStmt.close();
×
470

471
  if ( !zPkIdxName.empty() )
×
472
  {
473
    int nKey = 0;
×
474
    int nCol = 0;
×
475
    truePk = 0;
×
476
    pStmt.prepare( db, "PRAGMA %s.index_xinfo=%Q", zDb.c_str(), zPkIdxName.c_str() );
×
477
    while ( SQLITE_ROW == ( rc = sqlite3_step( pStmt.get() ) ) )
×
478
    {
479
      nCol++;
×
480
      if ( sqlite3_column_int( pStmt.get(), 5 ) ) { nKey++; continue; }
×
481
      if ( sqlite3_column_int( pStmt.get(), 1 ) >= 0 ) truePk = 1;
×
482
    }
483
    if ( rc != SQLITE_DONE )
×
484
    {
485
      logSqliteError( context, db, "Failed to get list of primary keys for table " + tableName );
×
486
    }
487

488
    if ( nCol == nKey ) truePk = 1;
×
489
    if ( truePk )
×
490
    {
491
      nPK = nKey;
×
492
    }
493
    else
494
    {
495
      nPK = 1;
×
496
    }
497
    pStmt.close();
×
498
  }
499
  else
500
  {
501
    truePk = 1;
×
502
    nPK = 1;
×
503
  }
504
  pStmt.prepare( db, "PRAGMA %s.table_info=%Q", zDb.c_str(), zTab );
×
505

506
  naz = nPK;
×
507
  az.resize( naz );
×
508
  while ( SQLITE_ROW == ( rc = sqlite3_step( pStmt.get() ) ) )
×
509
  {
510
    int iPKey;
511
    std::string name = reinterpret_cast<const char *>( sqlite3_column_text( pStmt.get(), 1 ) );
×
512
    if ( truePk && ( iPKey = sqlite3_column_int( pStmt.get(), 5 ) ) > 0 )
×
513
    {
514
      az[iPKey - 1] = name;
×
515
    }
516
    else
517
    {
518
      az.push_back( name );
×
519
    }
520
  }
×
521
  if ( rc != SQLITE_DONE )
×
522
  {
523
    logSqliteError( context, db, "Failed to get list of primary keys for table " + tableName );
×
524
  }
525
  pStmt.close();
×
526

527
  /* If this table has an implicit rowid for a PK, figure out how to refer
528
  ** to it. There are three options - "rowid", "_rowid_" and "oid". Any
529
  ** of these will work, unless the table has an explicit column of the
530
  ** same name.  */
531
  if ( az[0].empty() )
×
532
  {
533
    size_t j;
534
    std::vector<std::string> azRowid = { "rowid", "_rowid_", "oid" };
×
535
    for ( size_t i = 0; i < azRowid.size(); i++ )
×
536
    {
537
      for ( j = 1; j < naz; j++ )
×
538
      {
539
        if ( az[j] == azRowid[i] ) break;
×
540
      }
541
      if ( j >= naz )
×
542
      {
543
        az[0] = azRowid[i];
×
544
        break;
×
545
      }
546
    }
547
    if ( az[0].empty() )
×
548
    {
549
      az.clear();
×
550
    }
551
  }
×
552

553
  return az;
×
554
}
×
555

556
std::string sqliteErrorMessage( sqlite3 *db, const std::string &description )
3✔
557
{
558
  std::string errorMessage;
3✔
559
  if ( db )
3✔
560
  {
561
    std::string errMsg = std::string( sqlite3_errmsg( db ) );
3✔
562
    std::string errCode = std::to_string( sqlite3_extended_errcode( db ) );
3✔
563
    errorMessage = description + " (SQLITE3 error [" + errCode + "]: " + errMsg + ")";
3✔
564
  }
3✔
565
  else
566
  {
567
    errorMessage = description + " (unknown SQLite error)";
×
568
  }
569
  return errorMessage;
3✔
570
}
×
571

572
void logSqliteError( const Context *context, std::shared_ptr<Sqlite3Db> db, const std::string &description )
×
573
{
574
  std::string errMsg = sqliteErrorMessage( db->get(), description );
×
575
  context->logger().error( errMsg );
×
576
}
×
577

578
void throwSqliteError( sqlite3 *db, const std::string &description )
3✔
579
{
580
  std::string errMsg = sqliteErrorMessage( db, description );
3✔
581
  // Special-case errors caused by conflicts with DB constraints
582
  if ( sqlite3_errcode( db ) == SQLITE_CONSTRAINT )
3✔
583
    throw GeoDiffConflictsException( errMsg );
2✔
584
  else
585
    throw GeoDiffException( errMsg );
1✔
586
}
3✔
587

588
int parseGpkgbHeaderSize( const std::string &gpkgWkb )
78✔
589
{
590
  // see GPKG binary header definition http://www.geopackage.org/spec/#gpb_spec
591

592
  char flagByte = gpkgWkb[ GPKG_FLAG_BYTE_POS ];
78✔
593

594
  char envelope_byte = ( flagByte & GPKG_ENVELOPE_SIZE_MASK ) >> 1;
78✔
595
  int envelope_size = 0;
78✔
596

597
  switch ( envelope_byte )
78✔
598
  {
599
    case 1:
16✔
600
      envelope_size = 32;
16✔
601
      break;
16✔
602

603
    case 2:
15✔
604
    // fall through
605
    case 3:
606
      envelope_size = 48;
15✔
607
      break;
15✔
608

UNCOV
609
    case 4:
×
UNCOV
610
      envelope_size = 64;
×
UNCOV
611
      break;
×
612

613
    default:
47✔
614
      envelope_size = 0;
47✔
615
      break;
47✔
616
  }
617

618
  return GPKG_NO_ENVELOPE_HEADER_SIZE + envelope_size;
78✔
619
}
620

621
std::string createGpkgHeader( std::string &wkb, const TableColumnInfo &col )
68✔
622
{
623
  // initialize instream with wkb
624
  binstream_t inStream;
625
  uint8_t *dataPtr = reinterpret_cast<uint8_t *>( &wkb[0] );
68✔
626
  size_t len = wkb.size();
68✔
627

628
  if ( binstream_init( &inStream, dataPtr, len ) != SQLITE_OK )
68✔
UNCOV
629
    throw GeoDiffException( "Could initialize binary stream for GeoPackage header" );
×
630

631
  // fill envelope
632
  geom_envelope_t envelope;
633
  errorstream_t err;
634
  if ( wkb_fill_envelope( &inStream, WKB_ISO, &envelope, &err ) != SQLITE_OK )
68✔
635
  {
UNCOV
636
    std::string error( error_message( &err ) );
×
UNCOV
637
    throw GeoDiffException( "Could not fill envelope for GeoPackage header: " + error );
×
UNCOV
638
  }
×
639

640
  bool geomIsEmpty = geom_envelope_finalize( &envelope );
68✔
641

642
  // initialize outstream for header
643
  binstream_t outStream;
644
  if ( binstream_init_growable( &outStream, GPKG_NO_ENVELOPE_HEADER_SIZE ) != SQLITE_OK )
68✔
UNCOV
645
    throw GeoDiffException( "Could initialize growing binary stream for GeoPackage header" );
×
646

647
  geom_blob_header_t gpbHeader;
648
  gpbHeader.empty = geomIsEmpty;
68✔
649
  gpbHeader.version = 0;
68✔
650
  gpbHeader.srid = col.geomSrsId;
68✔
651
  gpbHeader.envelope = envelope;
68✔
652

653
  // change GeoPackage envelope sizes to imitate GDAL:
654
  //  a) ignore M coordinates
655
  //  b) do not write envelope if geometry is simple point
656
  gpbHeader.envelope.has_env_m = 0;
68✔
657

658
  if ( col.geomType == "POINT" )
68✔
659
  {
660
    gpbHeader.envelope.has_env_x = 0;
45✔
661
    gpbHeader.envelope.has_env_y = 0;
45✔
662
    gpbHeader.envelope.has_env_z = 0;
45✔
663
  }
664

665
  // write header to outstream
666
  if ( gpb_write_header( &outStream, &gpbHeader, &err ) != SQLITE_OK )
68✔
667
  {
UNCOV
668
    std::string error( error_message( &err ) );
×
UNCOV
669
    throw GeoDiffException( "Could not create GeoPackage header: " + error );
×
UNCOV
670
  }
×
671

672
  /*
673
   *  From documentation we know that outStream->position is now (after filling header struct) an index
674
   *  of the first byte after header ~ WKB Data start. Position can thus be used as a header size.
675
   */
676
  const void *headerDataPtr = reinterpret_cast<void *>( outStream.data );
68✔
677

678
  std::string header( outStream.position, 0 );
68✔
679
  memcpy( &header[0], headerDataPtr, outStream.position );
68✔
680

681
  binstream_destroy( &inStream, 1 );
68✔
682
  binstream_destroy( &outStream, 1 );
68✔
683

684
  return header;
136✔
UNCOV
685
}
×
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