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

MerginMaps / geodiff / 21714362832

05 Feb 2026 01:59PM UTC coverage: 88.066% (+0.003%) from 88.063%
21714362832

push

github

wonder-sk
Fix CI-built geodiff CLI

3638 of 4131 relevant lines covered (88.07%)

573.54 hits per line

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

62.38
/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
  const char *errmsg = "";
350✔
238
  rc = sqlite3_gpkg_auto_init( db->get(), &errmsg, NULL );
350✔
239
  if ( rc != SQLITE_OK )
350✔
240
  {
241
    throw GeoDiffException( "Failed to initialize GPKG extension [" + std::to_string( rc ) + "]: " + std::string( errmsg ) );
×
242
  }
243
}
350✔
244

245

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

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

257

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

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

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

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

323

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

329
  ForeignKeys ret;
×
330

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

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

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

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

372
  return ret;
×
373
}
×
374

375

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

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

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

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

430

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

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

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

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

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

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

554
  return az;
×
555
}
×
556

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

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

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

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

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

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

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

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

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

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

619
  return GPKG_NO_ENVELOPE_HEADER_SIZE + envelope_size;
78✔
620
}
621

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

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

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

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

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

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

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

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

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

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

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

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

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

© 2026 Coveralls, Inc