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

MerginMaps / geodiff / 20332653776

18 Dec 2025 09:42AM UTC coverage: 88.298% (-1.7%) from 90.035%
20332653776

push

github

wonder-sk
Fix updating rows with microsecond timestamps in Postgres

1 of 1 new or added line in 1 file covered. (100.0%)

237 existing lines in 6 files now uncovered.

3584 of 4059 relevant lines covered (88.3%)

582.99 hits per line

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

87.03
/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 )
46✔
48
  : std::exception()
49
  , mMsg( msg )
46✔
50
{
51
}
46✔
52

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

58
// ////////////////////////////////////////////////////////////////////////
59
// ////////////////////////////////////////////////////////////////////////
60
// ////////////////////////////////////////////////////////////////////////
61

62
// ////////////////////////////////////////////////////////////////////////
63
// ////////////////////////////////////////////////////////////////////////
64
// ////////////////////////////////////////////////////////////////////////
65

66
Buffer::Buffer() = default;
1,513✔
67

68
Buffer::~Buffer()
1,513✔
69
{
70
  free();
1,513✔
71
}
1,513✔
72

73
void Buffer::free()
2,364✔
74
{
75
  if ( mZ )
2,364✔
76
  {
77
    sqlite3_free( mZ );
1,377✔
78
    mZ = nullptr;
1,377✔
79
    mAlloc = 0;
1,377✔
80
    mUsed = 0;
1,377✔
81
  }
82
}
2,364✔
83

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

88
  // clean the buffer
89
  free();
851✔
90

91
  /* Open the file */
92
  FILE *fp = openFile( filename, "rb" );
851✔
93
  if ( nullptr == fp )
851✔
94
  {
95
    throw GeoDiffException( "Unable to open " + filename );
9✔
96
  }
97

98
  /* Seek to the end of the file */
99
  int rc = fseek( fp, 0L, SEEK_END );
842✔
100
  if ( 0 != rc )
842✔
101
  {
UNCOV
102
    fclose( fp );
×
UNCOV
103
    throw GeoDiffException( "Unable to seek the end of " + filename );
×
104
  }
105

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

116
  if ( mAlloc == 0 )
842✔
117
  {
118
    // empty file
119
    fclose( fp );
127✔
120
    return;
127✔
121
  }
122

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

131
  /* Rewind file pointer to start of file */
132
  rewind( fp );
715✔
133

134
  /* Slurp file into buffer */
135
  if ( mAlloc != ( int ) fread( mZ, 1, mAlloc, fp ) )
715✔
136
  {
UNCOV
137
    fclose( fp );
×
UNCOV
138
    throw GeoDiffException( "Unable to read " + filename + " to internal buffer" );
×
139
  }
140

141
  /* Close the file */
142
  if ( EOF == fclose( fp ) )
715✔
143
  {
UNCOV
144
    throw GeoDiffException( "Unable to close " + filename );
×
145
  }
146
}
147

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

179
const char *Buffer::c_buf() const
24,440✔
180
{
181
  return mZ;
24,440✔
182
}
183

184
int Buffer::size() const
27,716✔
185
{
186
  return mAlloc;
27,716✔
187
}
188

189
// ////////////////////////////////////////////////////////////////////////
190

191

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

204
//
205

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

220
bool filecopy( const std::string &to, const std::string &from )
62✔
221
{
222
  fileremove( to );
62✔
223

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

239
  dst << src.rdbuf();
62✔
240
#endif
241
  return true;
62✔
242
}
62✔
243

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

266
bool fileexists( const std::string &path )
1,634✔
267
{
268
#ifdef WIN32
269
  try
270
  {
271
    std::wstring wPath = stringToWString( path );
272
    if ( wPath.empty() )
273
      return false;
274

275
    return PathFileExists( wPath.c_str() );
276
  }
277
  catch ( GeoDiffException & )
278
  {
279
    return false;
280
  }
281
#else
282
  // https://stackoverflow.com/a/12774387/2838364
283
  struct stat buffer;
284
  return ( stat( path.c_str(), &buffer ) == 0 );
1,634✔
285
#endif
286
}
287

288
bool startsWith( const std::string &str, const std::string &substr )
23,427✔
289
{
290
  if ( str.size() < substr.size() )
23,427✔
291
    return false;
3,454✔
292

293
  return str.rfind( substr, 0 ) == 0;
19,973✔
294
}
295

296
std::string replace( const std::string &str, const std::string &substr, const std::string &replacestr )
2,925✔
297
{
298
  std::string res( str );
2,925✔
299

300
  size_t i = 0;
2,925✔
301
  while ( res.find( substr,  i ) != std::string::npos )
2,937✔
302
  {
303
    i = res.find( substr, i );
12✔
304
    res.replace( i, substr.size(), replacestr );
12✔
305
    i = i + replacestr.size();
12✔
306
  }
307
  return res;
2,925✔
UNCOV
308
}
×
309

310

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

328
  // table handled by triggers trigger_*_feature_count_*
UNCOV
329
  if ( startsWith( tableName, "gpkg_" ) )
×
UNCOV
330
    return false;
×
331
  // table handled by triggers rtree_*_geometry_*
UNCOV
332
  if ( startsWith( tableName, "rtree_" ) )
×
UNCOV
333
    return false;
×
334
  // internal table for AUTOINCREMENT
UNCOV
335
  if ( tableName == "sqlite_sequence" )
×
UNCOV
336
    return false;
×
337

UNCOV
338
  return true;
×
339
}
340

341

342
////
343

344
void get_primary_key( const ChangesetEntry &entry, int &fid, int &nColumn )
304✔
345
{
346
  const std::vector<bool> &tablePkeys = entry.table->primaryKeys;
304✔
347

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

369
  nColumn = ( int ) pk_column_number;
303✔
370

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

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

408

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

431
std::string getEnvVar( std::string const &key, const std::string &defaultVal )
836✔
432
{
433
  std::string ret = defaultVal;
836✔
434

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

449
  return ret;
836✔
UNCOV
450
}
×
451

452
int getEnvVarInt( std::string const &key, int defaultVal )
251✔
453
{
454
  std::string envVal = getEnvVar( key, "" );
251✔
455
  if ( envVal.empty() )
251✔
456
    return defaultVal;
251✔
457
  else
UNCOV
458
    return atoi( envVal.c_str() );
×
459
}
251✔
460

461
std::string tmpdir()
557✔
462
{
463
#ifdef WIN32
464
  wchar_t arr[MAX_PATH];
465
  DWORD dwRetVal = GetTempPathW( MAX_PATH, arr );
466

467
  if ( dwRetVal > MAX_PATH || ( dwRetVal == 0 ) )
468
  {
469
    return std::string( "C:/temp/" );
470
  }
471

472
  try
473
  {
474
    std::wstring tempDirPath( arr );
475
    return wstringToString( tempDirPath );
476
  }
477
  catch ( GeoDiffException & )
478
  {
479
    return std::string();
480
  }
481

482
#else
483
  return getEnvVar( "TMPDIR", "/tmp/" );
2,228✔
484
#endif
485
}
486

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

497
    return wStr;
498
  }
499
  catch ( const std::range_error & )
500
  {
501
    throw GeoDiffException( "Unable to convert UTF-8 to UTF-16." );
502
  }
503
}
504

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

514
    return str;
515
  }
516
  catch ( const std::range_error & )
517
  {
518
    throw GeoDiffException( "Unable to convert UTF-8 to UTF-16." );
519
  }
520
}
521
#endif
522

523
TmpFile::TmpFile( ) = default;
33✔
524

525

526
TmpFile::TmpFile( const std::string &path )
228✔
527
  : mPath( path )
228✔
528
{
529
}
228✔
530

531
TmpFile::~TmpFile()
261✔
532
{
533
  if ( fileexists( mPath ) )
261✔
534
  {
535
    fileremove( mPath );
247✔
536
  }
537
}
261✔
538

539
const std::string &TmpFile::path() const
110✔
540
{
541
  return mPath;
110✔
542
}
543

544
const char *TmpFile::c_path() const
430✔
545
{
546
  return mPath.c_str();
430✔
547
}
548

549
void TmpFile::setPath( const std::string &path )
24✔
550
{
551
  mPath = path;
24✔
552
}
24✔
553

554
ConflictFeature::ConflictFeature( int pk,
19✔
555
                                  const std::string &tableName )
19✔
556
  : mPk( pk )
19✔
557
  , mTableName( tableName )
19✔
558
{
559
}
19✔
560

561
bool ConflictFeature::isValid() const
19✔
562
{
563
  return !mItems.empty();
19✔
564
}
565

566
void ConflictFeature::addItem( const ConflictItem &item )
7✔
567
{
568
  mItems.push_back( item );
7✔
569
}
7✔
570

571
const std::string &ConflictFeature::tableName() const
13✔
572
{
573
  return mTableName;
13✔
574
}
575

576
int ConflictFeature::pk() const
6✔
577
{
578
  return mPk;
6✔
579
}
580

581
const std::vector<ConflictItem> &ConflictFeature::items() const
6✔
582
{
583
  return mItems;
6✔
584
}
585

586
ConflictItem::ConflictItem( int column, const Value &base,
7✔
587
                            const Value &theirs, const Value &ours )
7✔
588
  : mColumn( column )
7✔
589
  , mBase( base )
7✔
590
  , mTheirs( theirs )
7✔
591
  , mOurs( ours )
7✔
592
{
593

594
}
7✔
595

596
Value ConflictItem::base() const
7✔
597
{
598
  return mBase;
7✔
599
}
600

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

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

611
int ConflictItem::column() const
7✔
612
{
613
  return mColumn;
7✔
614
}
615

UNCOV
616
int indexOf( const std::vector<std::string> &arr, const std::string &val )
×
617
{
UNCOV
618
  std::vector<std::string>::const_iterator result = std::find( arr.begin(), arr.end(), val );
×
UNCOV
619
  if ( result == arr.end() )
×
UNCOV
620
    return -1;
×
621
  else
UNCOV
622
    return ( int ) std::distance( arr.begin(), result );
×
623
}
624

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

637
std::string lowercaseString( const std::string &str )
4,216✔
638
{
639
  std::string ret = str;
4,216✔
640
#ifdef WIN32
641
  std::transform( ret.begin(), ret.end(), ret.begin(), []( unsigned char i ) { return ( unsigned char ) std::tolower( i ); } );
642
#else
643
  std::transform( ret.begin(), ret.end(), ret.begin(), []( unsigned char i ) { return std::tolower( i ); } );
31,901✔
644
#endif
645
  return ret;
4,216✔
646
}
647

648

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

666
std::string randomTmpFilename()
19✔
667
{
668
  std::string tdir = tmpdir();
19✔
669
  if ( tdir.empty() )
19✔
UNCOV
670
    return tdir;
×
671
  else
672
    return tmpdir() + "geodiff_" + randomString( 6 );
19✔
673
}
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

© 2026 Coveralls, Inc