• 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

87.19
/geodiff/src/geodiffutils.cpp
1
/*
2
 GEODIFF - MIT License
3
 Copyright (C) 2019 Peter Petrik
4
*/
5

6
#ifdef WIN32
7
// codecvt is deprecated, but no replacement API is available.
8
#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
9
#endif
10

11
#include "geodiff.h"
12
#include "geodiffutils.hpp"
13
#include "changeset.h"
14
#include "geodifflogger.hpp"
15
#include "geodiffcontext.hpp"
16

17
#include <stdio.h>
18
#include <stdlib.h>
19
#include <stdarg.h>
20
#include <ctype.h>
21
#include <string.h>
22
#include <sqlite3.h>
23
#include <exception>
24
#include <fstream>
25
#include <string>
26
#include <vector>
27
#include <iostream>
28
#include <sstream>
29
#include <algorithm>
30
#include <cctype>
31
#include <gpkg.h>
32
#include <locale>
33
#include <limits>
34

35
#ifdef WIN32
36
#define UNICODE
37
#include <windows.h>
38
#include <tchar.h>
39
#include <Shlwapi.h>
40
#include <codecvt>
41
#else
42
#include <unistd.h>
43
#include <errno.h>
44
#include <sys/stat.h>
45
#endif
46

47
GeoDiffException::GeoDiffException( const std::string &msg )
54✔
48
  : std::exception()
49
  , mMsg( msg )
54✔
50
{
51
}
54✔
52

53
const char *GeoDiffException::what() const noexcept
38✔
54
{
55
  return mMsg.c_str();
38✔
56
}
57

58
void GeoDiffException::addContext( const std::string &msg )
8✔
59
{
60
  mMsg = msg + "\n" + mMsg;
8✔
61
}
8✔
62

63
// ////////////////////////////////////////////////////////////////////////
64
// ////////////////////////////////////////////////////////////////////////
65
// ////////////////////////////////////////////////////////////////////////
66

67
// ////////////////////////////////////////////////////////////////////////
68
// ////////////////////////////////////////////////////////////////////////
69
// ////////////////////////////////////////////////////////////////////////
70

71
Buffer::Buffer() = default;
1,513✔
72

73
Buffer::~Buffer()
1,513✔
74
{
75
  free();
1,513✔
76
}
1,513✔
77

78
void Buffer::free()
2,364✔
79
{
80
  if ( mZ )
2,364✔
81
  {
82
    sqlite3_free( mZ );
1,377✔
83
    mZ = nullptr;
1,377✔
84
    mAlloc = 0;
1,377✔
85
    mUsed = 0;
1,377✔
86
  }
87
}
2,364✔
88

89
void Buffer::read( const std::string &filename )
851✔
90
{
91
  // https://stackoverflow.com/questions/3747086/reading-the-whole-text-file-into-a-char-array-in-c
92

93
  // clean the buffer
94
  free();
851✔
95

96
  /* Open the file */
97
  FILE *fp = openFile( filename, "rb" );
851✔
98
  if ( nullptr == fp )
851✔
99
  {
100
    throw GeoDiffException( "Unable to open " + filename );
9✔
101
  }
102

103
  /* Seek to the end of the file */
104
  int rc = fseek( fp, 0L, SEEK_END );
842✔
105
  if ( 0 != rc )
842✔
106
  {
UNCOV
107
    fclose( fp );
×
UNCOV
108
    throw GeoDiffException( "Unable to seek the end of " + filename );
×
109
  }
110

111
  long off_end;
112
  /* Byte offset to the end of the file (size) */
113
  if ( 0 > ( off_end = ftell( fp ) ) )
842✔
114
  {
UNCOV
115
    fclose( fp );
×
UNCOV
116
    throw GeoDiffException( "Unable to read file size of " + filename );
×
117
  }
118
  mAlloc = ( size_t )off_end;
842✔
119
  mUsed = mAlloc;
842✔
120

121
  if ( mAlloc == 0 )
842✔
122
  {
123
    // empty file
124
    fclose( fp );
127✔
125
    return;
127✔
126
  }
127

128
  /* Allocate a buffer to hold the whole file */
129
  mZ = reinterpret_cast<char *>( sqlite3_malloc( mAlloc ) );
715✔
130
  if ( mZ == nullptr )
715✔
131
  {
UNCOV
132
    fclose( fp );
×
UNCOV
133
    throw GeoDiffException( "Out of memory to read " + filename + " to internal buffer" );
×
134
  }
135

136
  /* Rewind file pointer to start of file */
137
  rewind( fp );
715✔
138

139
  /* Slurp file into buffer */
140
  if ( mAlloc != ( int ) fread( mZ, 1, mAlloc, fp ) )
715✔
141
  {
UNCOV
142
    fclose( fp );
×
UNCOV
143
    throw GeoDiffException( "Unable to read " + filename + " to internal buffer" );
×
144
  }
145

146
  /* Close the file */
147
  if ( EOF == fclose( fp ) )
715✔
148
  {
UNCOV
149
    throw GeoDiffException( "Unable to close " + filename );
×
150
  }
151
}
152

153
void Buffer::printf( const char *zFormat, ... )
662✔
154
{
155
  int nNew;
156
  for ( ;; )
157
  {
158
    if ( mZ )
1,324✔
159
    {
160
      va_list ap;
161
      va_start( ap, zFormat );
662✔
162
      sqlite3_vsnprintf( mAlloc - mUsed, mZ + mUsed, zFormat, ap );
662✔
163
      va_end( ap );
662✔
164
      nNew = ( int )strlen( mZ + mUsed );
662✔
165
    }
166
    else
167
    {
168
      nNew = mAlloc;
662✔
169
    }
170
    if ( mUsed + nNew < mAlloc - 1 )
1,324✔
171
    {
172
      mUsed += nNew;
662✔
173
      break;
662✔
174
    }
175
    mAlloc = mAlloc * 2 + 1000;
662✔
176
    mZ = reinterpret_cast<char *>( sqlite3_realloc( mZ, mAlloc ) );
662✔
177
    if ( mZ == nullptr )
662✔
178
    {
UNCOV
179
      throw GeoDiffException( "out of memory in Buffer::printf" );
×
180
    }
181
  }
662✔
182
}
662✔
183

184
const char *Buffer::c_buf() const
24,440✔
185
{
186
  return mZ;
24,440✔
187
}
188

189
int Buffer::size() const
27,716✔
190
{
191
  return mAlloc;
27,716✔
192
}
193

194
// ////////////////////////////////////////////////////////////////////////
195

196

197
std::string to_string_with_max_precision( double a_value )
17✔
198
{
199
  std::ostringstream out;
17✔
200
  // The limits digits10 ( = 15 for double) and max_digits10 ( = 17 for double) are a bit confusing.
201
  // originally had numeric_limits::digits10 + 1 here, but that was not enough for some numbers.
202
  // Also, we used std::fixed to avoid scientific notation, but that's loosing precision for small
203
  // numbers like 0.000123... where we waste a couple of digits for leading zeros.
204
  out.precision( std::numeric_limits<double>::max_digits10 );
17✔
205
  out << a_value;
17✔
206
  return out.str();
34✔
207
}
17✔
208

209
//
210

211
FILE *openFile( const std::string &path, const std::string &mode )
851✔
212
{
213
#ifdef WIN32
214
  // convert string path to wstring
215
  FILE *fh;
216
  errno_t err = _wfopen_s( &fh, stringToWString( path ).c_str(), stringToWString( mode ).c_str() );
217
  if ( err )
218
    return nullptr;
219
  return fh;
220
#else
221
  return fopen( path.c_str(), mode.c_str() );
851✔
222
#endif
223
}
224

225
bool filecopy( const std::string &to, const std::string &from )
62✔
226
{
227
  fileremove( to );
62✔
228

229
#ifdef WIN32
230
  try
231
  {
232
    std::wstring wFrom = stringToWString( from );
233
    std::wstring wTo = stringToWString( to );
234
    CopyFile( wFrom.c_str(), wTo.c_str(), false );
235
  }
236
  catch ( GeoDiffException & )
237
  {
238
    return false;
239
  }
240
#else
241
  std::ifstream  src( from, std::ios::binary );
62✔
242
  std::ofstream  dst( to,   std::ios::binary );
62✔
243

244
  dst << src.rdbuf();
62✔
245
#endif
246
  return true;
62✔
247
}
62✔
248

249
bool fileremove( const std::string &path )
394✔
250
{
251
  if ( fileexists( path ) )
394✔
252
  {
253
#ifdef WIN32
254
    int res = 0;
255
    try
256
    {
257
      res = _wremove( stringToWString( path ).c_str() );
258
    }
259
    catch ( GeoDiffException & )
260
    {
261
      return false;
262
    }
263
#else
264
    int res = remove( path.c_str() );
255✔
265
#endif
266
    return res == 0;
255✔
267
  }
268
  return true;  // nothing to delete, no problem...
139✔
269
}
270

271
bool fileexists( const std::string &path )
1,564✔
272
{
273
#ifdef WIN32
274
  try
275
  {
276
    std::wstring wPath = stringToWString( path );
277
    if ( wPath.empty() )
278
      return false;
279

280
    return PathFileExists( wPath.c_str() );
281
  }
282
  catch ( GeoDiffException & )
283
  {
284
    return false;
285
  }
286
#else
287
  // https://stackoverflow.com/a/12774387/2838364
288
  struct stat buffer;
289
  return ( stat( path.c_str(), &buffer ) == 0 );
1,564✔
290
#endif
291
}
292

293
bool startsWith( const std::string &str, const std::string &substr )
23,427✔
294
{
295
  if ( str.size() < substr.size() )
23,427✔
296
    return false;
3,454✔
297

298
  return str.rfind( substr, 0 ) == 0;
19,973✔
299
}
300

301
std::string replace( const std::string &str, const std::string &substr, const std::string &replacestr )
2,925✔
302
{
303
  std::string res( str );
2,925✔
304

305
  size_t i = 0;
2,925✔
306
  while ( res.find( substr,  i ) != std::string::npos )
2,937✔
307
  {
308
    i = res.find( substr, i );
12✔
309
    res.replace( i, substr.size(), replacestr );
12✔
310
    i = i + replacestr.size();
12✔
311
  }
312
  return res;
2,925✔
UNCOV
313
}
×
314

315

UNCOV
316
bool isLayerTable( const std::string &tableName )
×
317
{
318
  /* typically geopackage from ogr would have these (table name is simple)
319
  gpkg_contents
320
  gpkg_extensions
321
  gpkg_geometry_columns
322
  gpkg_ogr_contents
323
  gpkg_spatial_ref_sys
324
  gpkg_tile_matrix
325
  gpkg_tile_matrix_set
326
  rtree_simple_geometry_node
327
  rtree_simple_geometry_parent
328
  rtree_simple_geometry_rowid
329
  simple (or any other name(s) of layers)
330
  sqlite_sequence
331
  */
332

333
  // table handled by triggers trigger_*_feature_count_*
UNCOV
334
  if ( startsWith( tableName, "gpkg_" ) )
×
335
    return false;
×
336
  // table handled by triggers rtree_*_geometry_*
UNCOV
337
  if ( startsWith( tableName, "rtree_" ) )
×
338
    return false;
×
339
  // internal table for AUTOINCREMENT
UNCOV
340
  if ( tableName == "sqlite_sequence" )
×
UNCOV
341
    return false;
×
342

UNCOV
343
  return true;
×
344
}
345

346

347
////
348

349
void get_primary_key( const ChangesetEntry &entry, int &fid, int &nColumn )
304✔
350
{
351
  const std::vector<bool> &tablePkeys = entry.table->primaryKeys;
304✔
352

353
  // lets assume for now it has only one PK and it is int...
354
  bool found_primary_key = false;
304✔
355
  size_t pk_column_number = 0;
304✔
356
  for ( size_t i = 0; i < tablePkeys.size(); ++i )
1,392✔
357
  {
358
    if ( tablePkeys[i] )
1,089✔
359
    {
360
      if ( found_primary_key )
305✔
361
      {
362
        // ups primary key composite!
363
        throw GeoDiffException( "internal error in _get_primary_key: support composite primary keys not implemented" );
3✔
364
      }
365
      pk_column_number = i;
304✔
366
      found_primary_key = true;
304✔
367
    }
368
  }
369
  if ( !found_primary_key )
303✔
370
  {
UNCOV
371
    throw GeoDiffException( "internal error in _get_primary_key: unable to find internal key" );
×
372
  }
373

374
  nColumn = ( int ) pk_column_number;
303✔
375

376
  // now get the value
377
  Value pkeyValue;
303✔
378
  if ( entry.op == ChangesetEntry::OpInsert )
303✔
379
  {
380
    pkeyValue = entry.newValues[pk_column_number];
203✔
381
  }
382
  else if ( entry.op == ChangesetEntry::OpDelete || entry.op == ChangesetEntry::OpUpdate )
100✔
383
  {
384
    pkeyValue = entry.oldValues[pk_column_number];
100✔
385
  }
386
  if ( pkeyValue.type() == Value::TypeUndefined || pkeyValue.type() == Value::TypeNull )
303✔
UNCOV
387
    throw GeoDiffException( "internal error in _get_primary_key: unable to get value of primary key" );
×
388

389
  if ( pkeyValue.type() == Value::TypeInt )
303✔
390
  {
391
    int64_t val = pkeyValue.getInt();
289✔
392
    fid = ( int ) val;
289✔
393
    return;
289✔
394
  }
395
  else if ( pkeyValue.type() == Value::TypeText )
14✔
396
  {
397
    std::string str = pkeyValue.getString();
14✔
398
    const char *strData = str.data();
14✔
399
    int hash = 0;
14✔
400
    size_t len = str.size();
14✔
401
    for ( size_t i = 0; i < len; i++ )
123✔
402
    {
403
      hash = 33 * hash + ( unsigned char )strData[i];
109✔
404
    }
405
    fid = hash;
14✔
406
  }
14✔
407
  else
408
  {
UNCOV
409
    throw GeoDiffException( "internal error in _get_primary_key: unsuported type of primary key" );
×
410
  }
411
}
303✔
412

413

414
bool flushString( const std::string &filename, const std::string &str )
74✔
415
{
416
#ifdef WIN32
417
  try
418
  {
419
    std::wstring wFilename = stringToWString( filename );
420
    std::ofstream out( wFilename );
421
    out << str;
422
    out.close();
423
  }
424
  catch ( GeoDiffException & )
425
  {
426
    return false;
427
  }
428
#else
429
  std::ofstream out( filename );
74✔
430
  out << str;
74✔
431
  out.close();
74✔
432
#endif
433
  return true;
74✔
434
}
74✔
435

436
std::string getEnvVar( std::string const &key, const std::string &defaultVal )
836✔
437
{
438
  std::string ret = defaultVal;
836✔
439

440
#ifdef WIN32
441
  char *val = nullptr;
442
  size_t sz = 0;
443
  if ( _dupenv_s( &val, &sz, key.c_str() ) == 0 && val != nullptr )
444
  {
445
    ret = std::string( val );
446
    free( val );
447
  }
448
#else
449
  char *val = getenv( key.c_str() );
836✔
450
  if ( val )
836✔
451
    ret = std::string( val );
56✔
452
#endif
453

454
  return ret;
836✔
UNCOV
455
}
×
456

457
int getEnvVarInt( std::string const &key, int defaultVal )
251✔
458
{
459
  std::string envVal = getEnvVar( key, "" );
251✔
460
  if ( envVal.empty() )
251✔
461
    return defaultVal;
251✔
462
  else
UNCOV
463
    return atoi( envVal.c_str() );
×
464
}
251✔
465

466
std::string tmpdir()
557✔
467
{
468
#ifdef WIN32
469
  wchar_t arr[MAX_PATH];
470
  DWORD dwRetVal = GetTempPathW( MAX_PATH, arr );
471

472
  if ( dwRetVal > MAX_PATH || ( dwRetVal == 0 ) )
473
  {
474
    return std::string( "C:/temp/" );
475
  }
476

477
  try
478
  {
479
    std::wstring tempDirPath( arr );
480
    return wstringToString( tempDirPath );
481
  }
482
  catch ( GeoDiffException & )
483
  {
484
    return std::string();
485
  }
486

487
#else
488
  return getEnvVar( "TMPDIR", "/tmp/" );
2,228✔
489
#endif
490
}
491

492
#ifdef WIN32
493
std::wstring stringToWString( const std::string &str )
494
{
495
  // we need to convert UTF-8 string to UTF-16 in order to use WindowsAPI
496
  // https://stackoverflow.com/questions/2573834/c-convert-string-or-char-to-wstring-or-wchar-t
497
  try
498
  {
499
    std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > converter;
500
    std::wstring wStr = converter.from_bytes( str );
501

502
    return wStr;
503
  }
504
  catch ( const std::range_error & )
505
  {
506
    throw GeoDiffException( "Unable to convert UTF-8 to UTF-16." );
507
  }
508
}
509

510
std::string wstringToString( const std::wstring &wStr )
511
{
512
  // we need to convert UTF-16 string to UTF-8 in order to use WindowsAPI
513
  // https://stackoverflow.com/questions/4804298/how-to-convert-wstring-into-string
514
  try
515
  {
516
    std::wstring_convert< std::codecvt_utf8_utf16< wchar_t > > converter;
517
    std::string str = converter.to_bytes( wStr );
518

519
    return str;
520
  }
521
  catch ( const std::range_error & )
522
  {
523
    throw GeoDiffException( "Unable to convert UTF-8 to UTF-16." );
524
  }
525
}
526
#endif
527

528
TmpFile::TmpFile( ) = default;
33✔
529

530

531
TmpFile::TmpFile( const std::string &path )
214✔
532
  : mPath( path )
214✔
533
{
534
}
214✔
535

536
TmpFile::~TmpFile()
247✔
537
{
538
  if ( fileexists( mPath ) )
247✔
539
  {
540
    fileremove( mPath );
233✔
541
  }
542
}
247✔
543

544
const std::string &TmpFile::path() const
110✔
545
{
546
  return mPath;
110✔
547
}
548

549
const char *TmpFile::c_path() const
402✔
550
{
551
  return mPath.c_str();
402✔
552
}
553

554
void TmpFile::setPath( const std::string &path )
24✔
555
{
556
  mPath = path;
24✔
557
}
24✔
558

559
ConflictFeature::ConflictFeature( int pk,
19✔
560
                                  const std::string &tableName )
19✔
561
  : mPk( pk )
19✔
562
  , mTableName( tableName )
19✔
563
{
564
}
19✔
565

566
bool ConflictFeature::isValid() const
19✔
567
{
568
  return !mItems.empty();
19✔
569
}
570

571
void ConflictFeature::addItem( const ConflictItem &item )
7✔
572
{
573
  mItems.push_back( item );
7✔
574
}
7✔
575

576
const std::string &ConflictFeature::tableName() const
13✔
577
{
578
  return mTableName;
13✔
579
}
580

581
int ConflictFeature::pk() const
6✔
582
{
583
  return mPk;
6✔
584
}
585

586
const std::vector<ConflictItem> &ConflictFeature::items() const
6✔
587
{
588
  return mItems;
6✔
589
}
590

591
ConflictItem::ConflictItem( int column, const Value &base,
7✔
592
                            const Value &theirs, const Value &ours )
7✔
593
  : mColumn( column )
7✔
594
  , mBase( base )
7✔
595
  , mTheirs( theirs )
7✔
596
  , mOurs( ours )
7✔
597
{
598

599
}
7✔
600

601
Value ConflictItem::base() const
7✔
602
{
603
  return mBase;
7✔
604
}
605

606
Value ConflictItem::theirs() const
7✔
607
{
608
  return mTheirs;
7✔
609
}
610

611
Value ConflictItem::ours() const
7✔
612
{
613
  return mOurs;
7✔
614
}
615

616
int ConflictItem::column() const
7✔
617
{
618
  return mColumn;
7✔
619
}
620

UNCOV
621
int indexOf( const std::vector<std::string> &arr, const std::string &val )
×
622
{
UNCOV
623
  std::vector<std::string>::const_iterator result = std::find( arr.begin(), arr.end(), val );
×
UNCOV
624
  if ( result == arr.end() )
×
UNCOV
625
    return -1;
×
626
  else
UNCOV
627
    return ( int ) std::distance( arr.begin(), result );
×
628
}
629

630
std::string concatNames( const std::vector<std::string> &names )
6✔
631
{
632
  std::string output;
6✔
633
  for ( const std::string &name : names )
16✔
634
  {
635
    if ( !output.empty() )
10✔
636
      output += ", ";
4✔
637
    output += name;
10✔
638
  }
639
  return output;
6✔
UNCOV
640
}
×
641

642
std::string lowercaseString( const std::string &str )
4,216✔
643
{
644
  std::string ret = str;
4,216✔
645
#ifdef WIN32
646
  std::transform( ret.begin(), ret.end(), ret.begin(), []( unsigned char i ) { return ( unsigned char ) std::tolower( i ); } );
647
#else
648
  std::transform( ret.begin(), ret.end(), ret.begin(), []( unsigned char i ) { return std::tolower( i ); } );
31,837✔
649
#endif
650
  return ret;
4,216✔
651
}
652

653

654
// from https://stackoverflow.com/questions/440133/how-do-i-create-a-random-alpha-numeric-string-in-c
655
std::string randomString( size_t length )
70✔
656
{
657
  auto randchar = []() -> char
420✔
658
  {
659
    const char charset[] =
420✔
660
    "0123456789"
661
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
662
    "abcdefghijklmnopqrstuvwxyz";
663
    const size_t max_index = ( sizeof( charset ) - 1 );
420✔
664
    return charset[ rand() % max_index ];
420✔
665
  };
666
  std::string str( length, 0 );
70✔
667
  std::generate_n( str.begin(), length, randchar );
70✔
668
  return str;
140✔
669
}
670

671
std::string randomTmpFilename()
19✔
672
{
673
  std::string tdir = tmpdir();
19✔
674
  if ( tdir.empty() )
19✔
UNCOV
675
    return tdir;
×
676
  else
677
    return tmpdir() + "geodiff_" + randomString( 6 );
19✔
678
}
19✔
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