• 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

74.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;
328✔
24
Sqlite3Db::~Sqlite3Db()
328✔
25
{
26
  close();
328✔
27
}
328✔
28

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

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

43
  if ( fileexists( filename ) )
20✔
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 );
20✔
49
  if ( rc )
20✔
50
  {
51
    throwSqliteError( mDb, "Unable to create " + filename + " as sqlite3 database" );
×
52
  }
53
}
20✔
54

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

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

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

78

79
///
80

81

82
Sqlite3Stmt::Sqlite3Stmt() = default;
5,326✔
83

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

89
sqlite3_stmt *Sqlite3Stmt::db_vprepare( sqlite3 *db, const char *zFormat, va_list ap )
4,977✔
90
{
91
  char *zSql;
92
  int rc;
93
  sqlite3_stmt *pStmt;
94

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

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

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

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

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

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

146
std::string Sqlite3Stmt::expandedSql() const
×
147
{
148
  char *str = sqlite3_expanded_sql( mStmt );
×
149
  std::string sql( str );
×
150
  sqlite3_free( str );
×
151
  return sql;
×
152
}
×
153

154

155
///
156

157

158
Sqlite3Value::Sqlite3Value() = default;
×
159

160
Sqlite3Value::Sqlite3Value( const sqlite3_value *ppValue )
1,183✔
161
{
162
  if ( ppValue )
1,183✔
163
  {
164
    mVal = sqlite3_value_dup( ppValue );
1,183✔
165
  }
166
}
1,183✔
167

168
Sqlite3Value::~Sqlite3Value()
1,183✔
169
{
170
  if ( mVal )
1,183✔
171
  {
172
    sqlite3_value_free( mVal );
1,183✔
173
  }
174
}
1,183✔
175

176
bool Sqlite3Value::isValid() const
×
177
{
178
  return mVal != nullptr;
×
179
}
180

181
sqlite3_value *Sqlite3Value::value() const
988✔
182
{
183
  return mVal;
988✔
184
}
185

186
std::string Sqlite3Value::toString( sqlite3_value *ppValue )
×
187
{
188
  if ( !ppValue )
×
189
    return "nil";
×
190
  std::string val = "n/a";
×
191
  int type = sqlite3_value_type( ppValue );
×
192
  if ( type == SQLITE_INTEGER )
×
193
    val = std::to_string( sqlite3_value_int( ppValue ) );
×
194
  else if ( type == SQLITE_TEXT )
×
195
    val = std::string( reinterpret_cast<const char *>( sqlite3_value_text( ppValue ) ) );
×
196
  else if ( type == SQLITE_FLOAT )
×
197
    val = std::to_string( sqlite3_value_double( ppValue ) );
×
198
  else if ( type == SQLITE_BLOB )
×
199
    val = "blob " + std::to_string( sqlite3_value_bytes( ppValue ) ) + " bytes";
×
200
  return val;
×
201
}
×
202

203
bool Sqlite3Value::operator==( const Sqlite3Value &other ) const
179✔
204
{
205
  sqlite3_value *v1 = mVal;
179✔
206
  sqlite3_value *v2 = other.mVal;
179✔
207

208
  int type1 = sqlite3_value_type( v1 );
179✔
209
  int type2 = sqlite3_value_type( v2 );
179✔
210
  if ( type1 != type2 )
179✔
211
    return false;
7✔
212

213
  if ( type1 == SQLITE_NULL )
172✔
214
    return true;
11✔
215
  else if ( type1 == SQLITE_INTEGER )
161✔
216
    return sqlite3_value_int64( v1 ) == sqlite3_value_int64( v2 );
79✔
217
  else if ( type1 == SQLITE_FLOAT )
82✔
218
    return sqlite3_value_double( v1 ) == sqlite3_value_double( v2 );
3✔
219
  else if ( type1 == SQLITE_TEXT )
79✔
220
  {
221
    return strcmp( ( const char * ) sqlite3_value_text( v1 ), ( const char * ) sqlite3_value_text( v2 ) ) == 0;
40✔
222
  }
223
  else if ( type1 == SQLITE_BLOB )
39✔
224
  {
225
    int len1 = sqlite3_value_bytes( v1 );
39✔
226
    int len2 = sqlite3_value_bytes( v2 );
39✔
227
    if ( len1 != len2 )
39✔
228
      return false;
×
229
    return memcmp( sqlite3_value_blob( v1 ), sqlite3_value_blob( v2 ), len1 ) == 0;
39✔
230
  }
231
  else
232
  {
233
    throw GeoDiffException( "Unexpected value type" );
×
234
  }
235
}
236

237

238
///
239

240

241
void register_gpkg_extensions( std::shared_ptr<Sqlite3Db> db )
297✔
242
{
243
  // register GPKG functions like ST_IsEmpty
244
  int rc = sqlite3_enable_load_extension( db->get(), 1 );
297✔
245
  if ( rc )
297✔
246
  {
247
    throwSqliteError( db->get(), "Failed to enable SQLite extensions loading" );
×
248
  }
249

250
  rc = sqlite3_gpkg_auto_init( db->get(), NULL, NULL );
297✔
251
  if ( rc )
297✔
252
  {
253
    throwSqliteError( db->get(), "Failed to initialize GPKG extension" );
×
254
  }
255
}
297✔
256

257

258
bool isGeoPackage( const Context *context, std::shared_ptr<Sqlite3Db> db )
296✔
259
{
260
  std::vector<std::string> tableNames;
296✔
261
  sqliteTables( context,
296✔
262
                db,
263
                "main",
264
                tableNames );
265

266
  return std::find( tableNames.begin(), tableNames.end(), "gpkg_contents" ) != tableNames.end();
592✔
267
}
296✔
268

269

270
void sqliteTriggers( const Context *context, std::shared_ptr<Sqlite3Db> db, std::vector<std::string> &triggerNames, std::vector<std::string> &triggerCmds )
92✔
271
{
272
  triggerNames.clear();
92✔
273
  triggerCmds.clear();
92✔
274

275
  Sqlite3Stmt statament;
92✔
276
  statament.prepare( db, "%s", "select name, sql from sqlite_master where type = 'trigger'" );
92✔
277
  int rc;
278
  while ( SQLITE_ROW == ( rc = sqlite3_step( statament.get() ) ) )
1,695✔
279
  {
280
    const char *name = ( char * ) sqlite3_column_text( statament.get(), 0 );
1,603✔
281
    const char *sql = ( char * ) sqlite3_column_text( statament.get(), 1 );
1,603✔
282

283
    if ( !name || !sql )
1,603✔
284
      continue;
1,602✔
285

286
    /* typically geopackage from ogr would have these (table name is simple)
287
        - gpkg_tile_matrix_zoom_level_insert
288
        - gpkg_tile_matrix_zoom_level_update
289
        - gpkg_tile_matrix_matrix_width_insert
290
        - gpkg_tile_matrix_matrix_width_update
291
        - gpkg_tile_matrix_matrix_height_insert
292
        - gpkg_tile_matrix_matrix_height_update
293
        - gpkg_tile_matrix_pixel_x_size_insert
294
        - gpkg_tile_matrix_pixel_x_size_update
295
        - gpkg_tile_matrix_pixel_y_size_insert
296
        - gpkg_tile_matrix_pixel_y_size_update
297
        - gpkg_metadata_md_scope_insert
298
        - gpkg_metadata_md_scope_update
299
        - gpkg_metadata_reference_reference_scope_insert
300
        - gpkg_metadata_reference_reference_scope_update
301
        - gpkg_metadata_reference_column_name_insert
302
        - gpkg_metadata_reference_column_name_update
303
        - gpkg_metadata_reference_row_id_value_insert
304
        - gpkg_metadata_reference_row_id_value_update
305
        - gpkg_metadata_reference_timestamp_insert
306
        - gpkg_metadata_reference_timestamp_update
307
        - rtree_simple_geometry_insert
308
        - rtree_simple_geometry_update1
309
        - rtree_simple_geometry_update2
310
        - rtree_simple_geometry_update3
311
        - rtree_simple_geometry_update4
312
        - rtree_simple_geometry_delete
313
        - trigger_insert_feature_count_simple
314
        - trigger_delete_feature_count_simple
315
     */
316
    const std::string triggerName( name );
1,603✔
317
    if ( startsWith( triggerName, "gpkg_" ) )
1,603✔
318
      continue;
790✔
319
    if ( startsWith( triggerName, "rtree_" ) )
813✔
320
      continue;
600✔
321
    if ( startsWith( triggerName, "trigger_insert_feature_count_" ) )
213✔
322
      continue;
106✔
323
    if ( startsWith( triggerName, "trigger_delete_feature_count_" ) )
107✔
324
      continue;
106✔
325
    triggerNames.push_back( name );
1✔
326
    triggerCmds.push_back( sql );
1✔
327
  }
1,603✔
328
  if ( rc != SQLITE_DONE )
92✔
329
  {
330
    logSqliteError( context, db, "Failed to get list of triggers" );
×
331
  }
332
  statament.close();
92✔
333
}
92✔
334

335

336
ForeignKeys sqliteForeignKeys( const Context *context, std::shared_ptr<Sqlite3Db> db, const std::string &dbName )
18✔
337
{
338
  std::vector<std::string> fromTableNames;
18✔
339
  sqliteTables( context, db, dbName, fromTableNames );
18✔
340

341
  ForeignKeys ret;
18✔
342

343
  for ( const std::string &fromTableName : fromTableNames )
156✔
344
  {
345
    if ( isLayerTable( fromTableName ) )
138✔
346
    {
347
      Sqlite3Stmt pStmt;     /* SQL statement being run */
28✔
348
      pStmt.prepare( db, "SELECT * FROM %s.pragma_foreign_key_list(%Q)", dbName.c_str(), fromTableName.c_str() );
28✔
349
      int rc;
350
      while ( SQLITE_ROW == ( rc = sqlite3_step( pStmt.get() ) ) )
32✔
351
      {
352
        const char *fk_to_table = ( const char * )sqlite3_column_text( pStmt.get(), 2 );
4✔
353
        const char *fk_from = ( const char * )sqlite3_column_text( pStmt.get(), 3 );
4✔
354
        const char *fk_to = ( const char * )sqlite3_column_text( pStmt.get(), 4 );
4✔
355

356
        if ( fk_to_table && fk_from && fk_to )
4✔
357
        {
358
          // TODO: this part is not speed-optimized and could be slower for databases with a lot of
359
          // columns and/or foreign keys. For each entry we grab column names again
360
          // and we search for index of value in plain std::vector array...
361
          std::vector<std::string> fromColumnNames = sqliteColumnNames( context, db, dbName, fromTableName );
4✔
362
          int fk_from_id = indexOf( fromColumnNames, fk_from );
4✔
363
          if ( fk_from_id < 0 )
4✔
364
            continue;
×
365

366
          std::vector<std::string> toColumnNames = sqliteColumnNames( context, db, dbName, fk_to_table );
8✔
367
          int fk_to_id = indexOf( toColumnNames, fk_to );
4✔
368
          if ( fk_to_id < 0 )
4✔
369
            continue;
×
370

371
          TableColumn from( fromTableName, fk_from_id );
4✔
372
          TableColumn to( fk_to_table, fk_to_id );
4✔
373
          ret.insert( std::pair<TableColumn, TableColumn>( from, to ) );
4✔
374
        }
4✔
375
      }
376
      if ( rc != SQLITE_DONE )
28✔
377
      {
378
        logSqliteError( context, db, "Failed to get list of foreing keys" );
×
379
      }
380
      pStmt.close();
28✔
381
    }
28✔
382
  }
383

384
  return ret;
36✔
385
}
18✔
386

387

388
void sqliteTables( const Context *context,
314✔
389
                   std::shared_ptr<Sqlite3Db> db,
390
                   const std::string &dbName,
391
                   std::vector<std::string> &tableNames )
392
{
393
  tableNames.clear();
314✔
394
  std::string all_tables_sql = "SELECT name FROM " + dbName + ".sqlite_master\n"
314✔
395
                               " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
396
                               " ORDER BY name";
314✔
397
  Sqlite3Stmt statament;
314✔
398
  statament.prepare( db, "%s", all_tables_sql.c_str() );
314✔
399
  int rc;
400
  while ( SQLITE_ROW == ( rc = sqlite3_step( statament.get() ) ) )
4,414✔
401
  {
402
    const char *name = ( const char * )sqlite3_column_text( statament.get(), 0 );
4,100✔
403
    if ( !name )
4,100✔
404
      continue;
1,781✔
405

406
    std::string tableName( name );
4,100✔
407
    /* typically geopackage from ogr would have these (table name is simple)
408
    gpkg_contents
409
    gpkg_extensions
410
    gpkg_geometry_columns
411
    gpkg_ogr_contents
412
    gpkg_spatial_ref_sys
413
    gpkg_tile_matrix
414
    gpkg_tile_matrix_set
415
    rtree_simple_geometry_node
416
    rtree_simple_geometry_parent
417
    rtree_simple_geometry_rowid
418
    simple (or any other name(s) of layers)
419
    sqlite_sequence
420
    */
421

422
    // table handled by triggers trigger_*_feature_count_*
423
    if ( startsWith( tableName, "gpkg_ogr_contents" ) )
4,100✔
424
      continue;
294✔
425
    // table handled by triggers rtree_*_geometry_*
426
    if ( startsWith( tableName, "rtree_" ) )
3,806✔
427
      continue;
1,185✔
428
    // internal table for AUTOINCREMENT
429
    if ( tableName == "sqlite_sequence" )
2,621✔
430
      continue;
302✔
431

432
    tableNames.push_back( tableName );
2,319✔
433
  }
4,100✔
434
  if ( rc != SQLITE_DONE )
314✔
435
  {
436
    logSqliteError( context, db, "Failed to get list of tables" );
×
437
  }
438
  statament.close();
314✔
439
  // result is ordered by name
440
}
314✔
441

442

443
/*
444
 * inspired by sqldiff.c function: columnNames()
445
 */
446
std::vector<std::string> sqliteColumnNames(
8✔
447
  const Context *context,
448
  std::shared_ptr<Sqlite3Db> db,
449
  const std::string &zDb,                /* Database ("main" or "aux") to query */
450
  const std::string &tableName   /* Name of table to return details of */
451
)
452
{
453
  std::vector<std::string> az;           /* List of column names to be returned */
8✔
454
  size_t naz = 0;             /* Number of entries in az[] */
8✔
455
  Sqlite3Stmt pStmt;     /* SQL statement being run */
8✔
456
  std::string zPkIdxName;    /* Name of the PRIMARY KEY index */
8✔
457
  int truePk = 0;          /* PRAGMA table_info indentifies the PK to use */
8✔
458
  int nPK = 0;             /* Number of PRIMARY KEY columns */
8✔
459

460
  /* Figure out what the true primary key is for the table.
461
  **   *  For WITHOUT ROWID tables, the true primary key is the same as
462
  **      the schema PRIMARY KEY, which is guaranteed to be present.
463
  **   *  For rowid tables with an INTEGER PRIMARY KEY, the true primary
464
  **      key is the INTEGER PRIMARY KEY.
465
  **   *  For all other rowid tables, the rowid is the true primary key.
466
  */
467
  const char *zTab = tableName.c_str();
8✔
468
  pStmt.prepare( db, "PRAGMA %s.index_list=%Q", zDb.c_str(), zTab );
8✔
469
  int rc;
470
  while ( SQLITE_ROW == ( rc = sqlite3_step( pStmt.get() ) ) )
8✔
471
  {
472
    if ( sqlite3_stricmp( ( const char * )sqlite3_column_text( pStmt.get(), 3 ), "pk" ) == 0 )
×
473
    {
474
      zPkIdxName = ( const char * ) sqlite3_column_text( pStmt.get(), 1 );
×
475
      break;
×
476
    }
477
  }
478
  if ( rc != SQLITE_DONE )
8✔
479
  {
480
    logSqliteError( context, db, "Failed to get list of primary keys for table " + tableName );
×
481
  }
482
  pStmt.close();
8✔
483

484
  if ( !zPkIdxName.empty() )
8✔
485
  {
486
    int nKey = 0;
×
487
    int nCol = 0;
×
488
    truePk = 0;
×
489
    pStmt.prepare( db, "PRAGMA %s.index_xinfo=%Q", zDb.c_str(), zPkIdxName.c_str() );
×
490
    while ( SQLITE_ROW == ( rc = sqlite3_step( pStmt.get() ) ) )
×
491
    {
492
      nCol++;
×
493
      if ( sqlite3_column_int( pStmt.get(), 5 ) ) { nKey++; continue; }
×
494
      if ( sqlite3_column_int( pStmt.get(), 1 ) >= 0 ) truePk = 1;
×
495
    }
496
    if ( rc != SQLITE_DONE )
×
497
    {
498
      logSqliteError( context, db, "Failed to get list of primary keys for table " + tableName );
×
499
    }
500

501
    if ( nCol == nKey ) truePk = 1;
×
502
    if ( truePk )
×
503
    {
504
      nPK = nKey;
×
505
    }
506
    else
507
    {
508
      nPK = 1;
×
509
    }
510
    pStmt.close();
×
511
  }
512
  else
513
  {
514
    truePk = 1;
8✔
515
    nPK = 1;
8✔
516
  }
517
  pStmt.prepare( db, "PRAGMA %s.table_info=%Q", zDb.c_str(), zTab );
8✔
518

519
  naz = nPK;
8✔
520
  az.resize( naz );
8✔
521
  while ( SQLITE_ROW == ( rc = sqlite3_step( pStmt.get() ) ) )
32✔
522
  {
523
    int iPKey;
524
    std::string name = ( char * )sqlite3_column_text( pStmt.get(), 1 );
24✔
525
    if ( truePk && ( iPKey = sqlite3_column_int( pStmt.get(), 5 ) ) > 0 )
24✔
526
    {
527
      az[iPKey - 1] = name;
8✔
528
    }
529
    else
530
    {
531
      az.push_back( name );
16✔
532
    }
533
  }
24✔
534
  if ( rc != SQLITE_DONE )
8✔
535
  {
536
    logSqliteError( context, db, "Failed to get list of primary keys for table " + tableName );
×
537
  }
538
  pStmt.close();
8✔
539

540
  /* If this table has an implicit rowid for a PK, figure out how to refer
541
  ** to it. There are three options - "rowid", "_rowid_" and "oid". Any
542
  ** of these will work, unless the table has an explicit column of the
543
  ** same name.  */
544
  if ( az[0].empty() )
8✔
545
  {
546
    size_t j;
547
    std::vector<std::string> azRowid = { "rowid", "_rowid_", "oid" };
×
548
    for ( size_t i = 0; i < azRowid.size(); i++ )
×
549
    {
550
      for ( j = 1; j < naz; j++ )
×
551
      {
552
        if ( az[j] == azRowid[i] ) break;
×
553
      }
554
      if ( j >= naz )
×
555
      {
556
        az[0] = azRowid[i];
×
557
        break;
×
558
      }
559
    }
560
    if ( az[0].empty() )
×
561
    {
562
      az.clear();
×
563
    }
564
  }
×
565

566
  return az;
16✔
567
}
8✔
568

569
std::string sqliteErrorMessage( sqlite3 *db, const std::string &description )
1✔
570
{
571
  std::string errorMessage;
1✔
572
  if ( db )
1✔
573
  {
574
    std::string errMsg = std::string( sqlite3_errmsg( db ) );
1✔
575
    std::string errCode = std::to_string( sqlite3_extended_errcode( db ) );
1✔
576
    errorMessage = description + " (SQLITE3 error [" + errCode + "]: " + errMsg + ")";
1✔
577
  }
1✔
578
  else
579
  {
580
    errorMessage = description + " (unknown SQLite error)";
×
581
  }
582
  return errorMessage;
1✔
583
}
×
584

585
void logSqliteError( const Context *context, std::shared_ptr<Sqlite3Db> db, const std::string &description )
×
586
{
587
  std::string errMsg = sqliteErrorMessage( db->get(), description );
×
588
  context->logger().error( errMsg );
×
589
}
×
590

591
void throwSqliteError( sqlite3 *db, const std::string &description )
1✔
592
{
593
  std::string errMsg = sqliteErrorMessage( db, description );
1✔
594
  throw GeoDiffException( errMsg );
1✔
595
}
1✔
596

597
int parseGpkgbHeaderSize( const std::string &gpkgWkb )
78✔
598
{
599
  // see GPKG binary header definition http://www.geopackage.org/spec/#gpb_spec
600

601
  char flagByte = gpkgWkb[ GPKG_FLAG_BYTE_POS ];
78✔
602

603
  char envelope_byte = ( flagByte & GPKG_ENVELOPE_SIZE_MASK ) >> 1;
78✔
604
  int envelope_size = 0;
78✔
605

606
  switch ( envelope_byte )
78✔
607
  {
608
    case 1:
16✔
609
      envelope_size = 32;
16✔
610
      break;
16✔
611

612
    case 2:
15✔
613
    // fall through
614
    case 3:
615
      envelope_size = 48;
15✔
616
      break;
15✔
617

618
    case 4:
×
619
      envelope_size = 64;
×
620
      break;
×
621

622
    default:
47✔
623
      envelope_size = 0;
47✔
624
      break;
47✔
625
  }
626

627
  return GPKG_NO_ENVELOPE_HEADER_SIZE + envelope_size;
78✔
628
}
629

630
std::string createGpkgHeader( std::string &wkb, const TableColumnInfo &col )
74✔
631
{
632
  // initialize instream with wkb
633
  binstream_t inStream;
634
  uint8_t *dataPtr = reinterpret_cast<uint8_t *>( &wkb[0] );
74✔
635
  size_t len = wkb.size();
74✔
636

637
  if ( binstream_init( &inStream, dataPtr, len ) != SQLITE_OK )
74✔
638
    throw GeoDiffException( "Could initialize binary stream for GeoPackage header" );
×
639

640
  // fill envelope
641
  geom_envelope_t envelope;
642
  errorstream_t err;
643
  if ( wkb_fill_envelope( &inStream, WKB_ISO, &envelope, &err ) != SQLITE_OK )
74✔
644
  {
645
    std::string error( error_message( &err ) );
×
646
    throw GeoDiffException( "Could not fill envelope for GeoPackage header: " + error );
×
647
  }
×
648

649
  bool geomIsEmpty = geom_envelope_finalize( &envelope );
74✔
650

651
  // initialize outstream for header
652
  binstream_t outStream;
653
  if ( binstream_init_growable( &outStream, GPKG_NO_ENVELOPE_HEADER_SIZE ) != SQLITE_OK )
74✔
654
    throw GeoDiffException( "Could initialize growing binary stream for GeoPackage header" );
×
655

656
  geom_blob_header_t gpbHeader;
657
  gpbHeader.empty = geomIsEmpty;
74✔
658
  gpbHeader.version = 0;
74✔
659
  gpbHeader.srid = col.geomSrsId;
74✔
660
  gpbHeader.envelope = envelope;
74✔
661

662
  // change GeoPackage envelope sizes to imitate GDAL:
663
  //  a) ignore M coordinates
664
  //  b) do not write envelope if geometry is simple point
665
  gpbHeader.envelope.has_env_m = 0;
74✔
666

667
  if ( col.geomType == "POINT" )
74✔
668
  {
669
    gpbHeader.envelope.has_env_x = 0;
51✔
670
    gpbHeader.envelope.has_env_y = 0;
51✔
671
    gpbHeader.envelope.has_env_z = 0;
51✔
672
  }
673

674
  // write header to outstream
675
  if ( gpb_write_header( &outStream, &gpbHeader, &err ) != SQLITE_OK )
74✔
676
  {
677
    std::string error( error_message( &err ) );
×
678
    throw GeoDiffException( "Could not create GeoPackage header: " + error );
×
679
  }
×
680

681
  /*
682
   *  From documentation we know that outStream->position is now (after filling header struct) an index
683
   *  of the first byte after header ~ WKB Data start. Position can thus be used as a header size.
684
   */
685
  const void *headerDataPtr = reinterpret_cast<void *>( outStream.data );
74✔
686

687
  std::string header( outStream.position, 0 );
74✔
688
  memcpy( &header[0], headerDataPtr, outStream.position );
74✔
689

690
  binstream_destroy( &inStream, 1 );
74✔
691
  binstream_destroy( &outStream, 1 );
74✔
692

693
  return header;
148✔
694
}
×
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