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

MerginMaps / geodiff / 20337604181

18 Dec 2025 12:53PM UTC coverage: 88.427%. First build
20337604181

Pull #236

github

web-flow
Merge b4844a14a into ff6ae36a1
Pull Request #236: Improve error handling in C and Python API

213 of 244 new or added lines in 6 files covered. (87.3%)

3614 of 4087 relevant lines covered (88.43%)

579.12 hits per line

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

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

6
#include "geodiff.h"
7
#include "geodiffutils.hpp"
8
#include "geodiffrebase.hpp"
9
#include "geodifflogger.hpp"
10
#include "geodiffcontext.hpp"
11

12
#include "driver.h"
13
#include "changesetreader.h"
14
#include "changesetutils.h"
15
#include "changesetwriter.h"
16

17
#include "sqliteutils.h"
18

19
#include <stdlib.h>
20
#include <stdarg.h>
21
#include <ctype.h>
22
#include <cstring>
23
#include <string.h>
24

25
#include <sqlite3.h>
26

27
#include <fcntl.h>
28
#include <iostream>
29
#include <string>
30
#include <vector>
31

32
#include "json.hpp"
33

34
/**
35
 * Logs message as error and sets error message for C API. To be used before
36
 * returning public function.
37
 */
38
static void setAndLogError( Context *context, std::string msg )
69✔
39
{
40
  context->logger().error( msg );
69✔
41
  context->setLastError( msg );
69✔
42
}
69✔
43

44
/**
45
 * Logs error message, converts exception to error code and sets error message
46
 * for C API.
47
 */
48
static int handleException( Context *context, const GeoDiffException &exc )
37✔
49
{
50
  setAndLogError( context, exc.what() );
37✔
51
  return exc.errorCode();
37✔
52
}
53

54
// use scripts/update_version.py to update the version here and in other places at once
55
const char *GEODIFF_version()
34✔
56
{
57
  return "2.0.4";
34✔
58
}
59

60
int GEODIFF_driverCount( GEODIFF_ContextH /*contextHandle*/ )
3✔
61
{
62
  const std::vector<std::string> drivers = Driver::drivers();
3✔
63
  return ( int ) drivers.size();
6✔
64
}
3✔
65

66
int GEODIFF_driverNameFromIndex( GEODIFF_ContextH contextHandle, int index, char *driverName )
6✔
67
{
68
  Context *context = static_cast<Context *>( contextHandle );
6✔
69
  if ( !context )
6✔
70
  {
71
    return GEODIFF_ERROR;
1✔
72
  }
73

74
  const std::vector<std::string> drivers = Driver::drivers();
5✔
75

76
  if ( ( size_t ) index >= drivers.size() )
5✔
77
  {
78
    setAndLogError( context, "Index out of range in GEODIFF_driverNameFromIndex" );
1✔
79
    return GEODIFF_ERROR;
1✔
80
  }
81

82
  const std::string name = drivers[index];
4✔
83
  const char *cname = name.c_str();
4✔
84
  const size_t len = name.size() + 1;
4✔
85
  assert( len < 256 );
4✔
86
  memcpy( driverName, cname, len );
4✔
87
  return GEODIFF_SUCCESS;
4✔
88
}
5✔
89

90
bool GEODIFF_driverIsRegistered( GEODIFF_ContextH contextHandle, const char *driverName )
5✔
91
{
92
  Context *context = static_cast<Context *>( contextHandle );
5✔
93
  if ( !context )
5✔
94
  {
95
    return GEODIFF_ERROR;
1✔
96
  }
97

98
  if ( !driverName )
4✔
99
  {
100
    setAndLogError( context, "NULL arguments to GEODIFF_driverIsRegistered" );
1✔
101
    return GEODIFF_ERROR;
1✔
102
  }
103

104
  return Driver::driverIsRegistered( std::string( driverName ) );
6✔
105
}
106

107
GEODIFF_ContextH GEODIFF_createContext()
147✔
108
{
109
  Context *context = new Context();
147✔
110

111
  sqlite3_initialize();
147✔
112

113
  return ( GEODIFF_ContextH ) context;
147✔
114
}
115

116
int GEODIFF_CX_setLoggerCallback( GEODIFF_ContextH contextHandle, GEODIFF_LoggerCallback loggerCallback )
29✔
117
{
118

119
  Context *context = static_cast<Context *>( contextHandle );
29✔
120
  if ( !context )
29✔
121
  {
122
    return GEODIFF_ERROR;
1✔
123
  }
124
  context->logger().setCallback( loggerCallback );
28✔
125
  return GEODIFF_SUCCESS;
28✔
126
}
127

128
int GEODIFF_CX_setMaximumLoggerLevel( GEODIFF_ContextH contextHandle,
131✔
129
                                      GEODIFF_LoggerLevel maxLogLevel )
130
{
131
  Context *context = static_cast<Context *>( contextHandle );
131✔
132
  if ( !context )
131✔
133
  {
134
    return GEODIFF_ERROR;
1✔
135
  }
136
  context->logger().setMaxLogLevel( maxLogLevel );
130✔
137
  return GEODIFF_SUCCESS;
130✔
138
}
139

140
int GEODIFF_CX_setTablesToSkip( GEODIFF_ContextH contextHandle, int tablesCount, const char **tablesToSkip )
6✔
141
{
142
  Context *context = static_cast<Context *>( contextHandle );
6✔
143
  if ( !context )
6✔
144
  {
145
    return GEODIFF_ERROR;
1✔
146
  }
147

148
  if ( tablesCount > 0 && !tablesToSkip )
5✔
149
  {
150
    setAndLogError( context, "NULL arguments to GEODIFF_CX_setTablesToSkip" );
1✔
151
    return GEODIFF_ERROR;
1✔
152
  }
153

154
  std::vector<std::string> tables;
4✔
155
  for ( int i = 0; i < tablesCount; ++i )
6✔
156
  {
157
    std::string tableName = tablesToSkip[i];
2✔
158
    tables.push_back( tableName );
2✔
159
  }
2✔
160

161
  context->setTablesToSkip( tables );
4✔
162
  return GEODIFF_SUCCESS;
4✔
163
}
4✔
164

165
const char *GEODIFF_CX_lastError( GEODIFF_ContextH contextHandle )
7✔
166
{
167
  const Context *context = static_cast<const Context *>( contextHandle );
7✔
168
  if ( !context )
7✔
NEW
169
    return "";
×
170
  return context->lastError().c_str();
7✔
171
}
172

173
void GEODIFF_CX_destroy( GEODIFF_ContextH contextHandle )
147✔
174
{
175
  Context *context = static_cast<Context *>( contextHandle );
147✔
176
  if ( context )
147✔
177
  {
178
    delete context;
147✔
179
    context = nullptr;
147✔
180
  }
181
}
147✔
182

183
int GEODIFF_createChangeset( GEODIFF_ContextH contextHandle, const char *base, const char *modified, const char *changeset )
157✔
184
{
185
  return GEODIFF_createChangesetEx( contextHandle, "sqlite", nullptr, base, modified, changeset );
157✔
186
}
187

188
int GEODIFF_applyChangeset( GEODIFF_ContextH contextHandle, const char *base, const char *changeset )
45✔
189
{
190
  return GEODIFF_applyChangesetEx( contextHandle, "sqlite", nullptr, base, changeset );
45✔
191
}
192

193
static void createChangesetEx( const Context *context, const char *driverName, const char *driverExtraInfo,
267✔
194
                               const char *base, const char *modified,
195
                               const char *changeset )
196
{
197
  if ( !driverName || !base || !modified || !changeset )
267✔
198
  {
199
    throw GeoDiffException( "NULL arguments to GEODIFF_createChangesetEx" );
3✔
200
  }
201

202
  std::map<std::string, std::string> conn;
266✔
203
  conn["base"] = std::string( base );
1,064✔
204
  conn["modified"] = std::string( modified );
798✔
205
  if ( driverExtraInfo )
266✔
206
    conn["conninfo"] = std::string( driverExtraInfo );
276✔
207
  std::unique_ptr<Driver> driver( Driver::createDriver( context, std::string( driverName ) ) );
266✔
208
  if ( !driver )
266✔
209
    throw GeoDiffException( "Unable to use driver: " + std::string( driverName ) );
3✔
210
  driver->open( conn );
265✔
211

212
  ChangesetWriter writer;
260✔
213
  writer.open( changeset );
260✔
214
  driver->createChangeset( writer );
260✔
215
}
290✔
216

217

218
int GEODIFF_createChangesetEx( GEODIFF_ContextH contextHandle, const char *driverName, const char *driverExtraInfo,
189✔
219
                               const char *base, const char *modified,
220
                               const char *changeset )
221
{
222
  Context *context = static_cast<Context *>( contextHandle );
189✔
223
  if ( !context )
189✔
224
  {
225
    return GEODIFF_ERROR;
1✔
226
  }
227

228
  try
229
  {
230
    createChangesetEx( context, driverName, driverExtraInfo, base, modified, changeset );
188✔
231
  }
232
  catch ( const  GeoDiffException &exc )
14✔
233
  {
234
    return handleException( context, exc );
14✔
235
  }
14✔
236

237
  return GEODIFF_SUCCESS;
174✔
238
}
239

240

241
static void makeCopy( const Context *context,
29✔
242
                      const char *driverSrcName,
243
                      const char *driverSrcExtraInfo,
244
                      const char *src,
245
                      const char *driverDstName,
246
                      const char *driverDstExtraInfo,
247
                      const char *dst )
248
{
249
  if ( !driverSrcName || !driverSrcExtraInfo || !driverDstName || !driverDstExtraInfo || !src || !dst )
29✔
250
  {
251
    throw GeoDiffException( "NULL arguments to GEODIFF_makeCopy" );
3✔
252
  }
253

254
  std::string srcDriverName( driverSrcName );
56✔
255
  std::string dstDriverName( driverDstName );
28✔
256
  std::unique_ptr<Driver> driverSrc( Driver::createDriver( context, srcDriverName ) );
28✔
257
  if ( !driverSrc )
28✔
258
  {
259
    throw GeoDiffException( "Cannot create driver " + srcDriverName );
1✔
260
  }
261

262
  std::unique_ptr<Driver> driverDst( Driver::createDriver( context, dstDriverName ) );
27✔
263
  if ( !driverDst )
27✔
264
  {
265
    throw GeoDiffException( "Cannot create driver " + dstDriverName );
1✔
266
  }
267

268
  TmpFile tmpFileChangeset( tmpdir( ) + "geodiff_changeset" + std::to_string( rand() ) );
26✔
269

270
  // open source
271
  std::map<std::string, std::string> connSrc;
26✔
272
  connSrc["base"] = std::string( src );
104✔
273
  connSrc["conninfo"] = std::string( driverSrcExtraInfo );
78✔
274
  driverSrc->open( connSrc );
26✔
275

276
  // get source tables
277
  std::vector<TableSchema> tables;
26✔
278
  std::vector<std::string> tableNames = driverSrc->listTables();
26✔
279

280
  if ( srcDriverName != dstDriverName )
26✔
281
  {
282
    for ( const std::string &tableName : tableNames )
64✔
283
    {
284
      TableSchema tbl = driverSrc->tableSchema( tableName );
44✔
285
      tableSchemaConvert( driverDstName, tbl );
44✔
286
      tables.push_back( tbl );
44✔
287
    }
44✔
288
  }
289
  else
290
  {
291
    for ( const std::string &tableName : tableNames )
18✔
292
    {
293
      TableSchema tbl = driverSrc->tableSchema( tableName );
12✔
294
      tables.push_back( tbl );
12✔
295
    }
12✔
296
  }
297

298
  // get source data
299
  {
300
    ChangesetWriter writer;
26✔
301
    writer.open( tmpFileChangeset.c_path() );
26✔
302
    driverSrc->dumpData( writer );
26✔
303
  }
26✔
304

305
  // create destination
306
  std::map<std::string, std::string> connDst;
26✔
307
  connDst["base"] = dst;
78✔
308
  connDst["conninfo"] = std::string( driverDstExtraInfo );
78✔
309
  driverDst->create( connDst, true );
26✔
310

311
  // create tables in destination
312
  driverDst->createTables( tables );
26✔
313

314
  // insert data to destination
315
  {
316
    ChangesetReader reader;
26✔
317
    reader.open( tmpFileChangeset.c_path() );
26✔
318
    driverDst->applyChangeset( reader );
26✔
319
  }
26✔
320

321
  // TODO: add spatial index to tables with geometry columns?
322
}
33✔
323

324

325
int GEODIFF_createChangesetDr( GEODIFF_ContextH contextHandle, const char *driverSrcName, const char *driverSrcExtraInfo, const char *src,
12✔
326
                               const char *driverDstName, const char *driverDstExtraInfo, const char *dst,
327
                               const char *changeset )
328
{
329
  Context *context = static_cast<Context *>( contextHandle );
12✔
330
  if ( !context )
12✔
331
  {
332
    return GEODIFF_ERROR;
1✔
333
  }
334

335
  if ( !driverSrcName || !driverSrcExtraInfo || !driverDstName || !driverDstExtraInfo || !src || !dst || !changeset )
11✔
336
  {
337
    setAndLogError( context, "NULL arguments to GEODIFF_createChangesetAcrossDrivers" );
1✔
338
    return GEODIFF_ERROR;
1✔
339
  }
340

341
  // check both driver names and connection details, for more context
342
  // see https://github.com/MerginMaps/geodiff/issues/185
343
  if ( strcmp( driverSrcName, driverDstName ) == 0 && strcmp( driverSrcExtraInfo, driverDstExtraInfo ) == 0 )
10✔
344
  {
345
    return GEODIFF_createChangesetEx( contextHandle, driverSrcName, driverSrcExtraInfo, src, dst, changeset );
6✔
346
  }
347

348
  // copy both sources to geopackage and create changeset
349
  TmpFile tmpSrcGpkg;
4✔
350
  TmpFile tmpDstGpkg;
4✔
351

352
  if ( strcmp( driverSrcName, Driver::SQLITEDRIVERNAME.c_str() ) != 0 )
4✔
353
  {
354
    tmpSrcGpkg.setPath( tmpdir( ) + "_gpkg-" + randomString( 6 ) );
3✔
355
    try
356
    {
357
      makeCopy( context, driverSrcName, driverSrcExtraInfo, src, Driver::SQLITEDRIVERNAME.c_str(), "", tmpSrcGpkg.c_path() );
3✔
358
    }
NEW
359
    catch ( GeoDiffException &exc )
×
360
    {
361

NEW
362
      exc.addContext( "Failed to create a copy of base source for driver " + std::string( driverSrcName ) );
×
NEW
363
      return handleException( context, exc );
×
364
    }
×
365
  }
366

367
  if ( strcmp( driverDstName, Driver::SQLITEDRIVERNAME.c_str() ) != 0 )
4✔
368
  {
369
    tmpDstGpkg.setPath( tmpdir() + "_gpkg-" + randomString( 6 ) );
2✔
370
    try
371
    {
372
      makeCopy( context, driverDstName, driverDstExtraInfo, dst, Driver::SQLITEDRIVERNAME.c_str(), "", tmpDstGpkg.c_path() );
2✔
373
    }
NEW
374
    catch ( GeoDiffException &exc )
×
375
    {
NEW
376
      exc.addContext( "Failed to create a copy of modified source for driver " + std::string( driverDstName ) );
×
NEW
377
      return handleException( context, exc );
×
378
    }
×
379
  }
380

381
  return GEODIFF_createChangesetEx(
11✔
382
           contextHandle,
383
           Driver::SQLITEDRIVERNAME.c_str(),
384
           "",
385
           tmpSrcGpkg.path().empty() ? src : tmpSrcGpkg.c_path(),
7✔
386
           tmpDstGpkg.path().empty() ? dst : tmpDstGpkg.c_path(),
6✔
387
           changeset );
4✔
388
}
4✔
389

390

391
static void applyChangesetEx(
91✔
392
  Context *context,
393
  const char *driverName,
394
  const char *driverExtraInfo,
395
  const char *base,
396
  const char *changeset )
397
{
398
  if ( !driverName || !base || !changeset )
91✔
399
  {
400
    throw GeoDiffException( "NULL arguments to GEODIFF_applyChangesetEx" );
3✔
401
  }
402

403
  std::map<std::string, std::string> conn;
90✔
404
  conn["base"] = std::string( base );
270✔
405
  if ( driverExtraInfo )
90✔
406
    conn["conninfo"] = std::string( driverExtraInfo );
180✔
407
  std::unique_ptr<Driver> driver( Driver::createDriver( context,  std::string( driverName ) ) );
90✔
408
  if ( !driver )
90✔
409
    throw GeoDiffException( "Unable to use driver: " + std::string( driverName ) );
3✔
410
  driver->open( conn );
89✔
411

412
  ChangesetReader reader;
89✔
413
  if ( !reader.open( changeset ) )
178✔
414
    throw GeoDiffException( "Unable to open changeset file for reading: " + std::string( changeset ) );
3✔
415
  if ( reader.isEmpty() )
88✔
416
  {
417
    context->logger().debug( "--- no changes ---" );
12✔
418
    return;
6✔
419
  }
420

421
  driver->applyChangeset( reader );
82✔
422
}
119✔
423

424
int GEODIFF_applyChangesetEx(
56✔
425
  GEODIFF_ContextH contextHandle,
426
  const char *driverName,
427
  const char *driverExtraInfo,
428
  const char *base,
429
  const char *changeset )
430
{
431
  Context *context = static_cast<Context *>( contextHandle );
56✔
432
  if ( !context )
56✔
433
  {
434
    return GEODIFF_ERROR;
1✔
435
  }
436

437
  try
438
  {
439
    applyChangesetEx( context, driverName, driverExtraInfo, base, changeset );
55✔
440
  }
441
  catch ( const  GeoDiffException &exc )
5✔
442
  {
443
    return handleException( context, exc );
5✔
444
  }
5✔
445

446
  return GEODIFF_SUCCESS;
50✔
447
}
448

449

450
int GEODIFF_createRebasedChangeset(
22✔
451
  GEODIFF_ContextH contextHandle, const char *base,
452
  const char *modified,
453
  const char *changeset_their,
454
  const char *changeset,
455
  const char *conflictfile )
456
{
457
  Context *context = static_cast<Context *>( contextHandle );
22✔
458
  if ( !context )
22✔
459
  {
460
    return GEODIFF_ERROR;
1✔
461
  }
462

463
  if ( !conflictfile )
21✔
464
  {
465
    setAndLogError( context, "NULL arguments to GEODIFF_createRebasedChangeset" );
1✔
466
    return GEODIFF_ERROR;
1✔
467
  }
468
  fileremove( conflictfile );
20✔
469

470
  try
471
  {
472
    // first verify if we are able to do rebase on this database schema at all
473
    {
474
      std::map<std::string, std::string> conn;
20✔
475
      conn["base"] = std::string( modified );
80✔
476
      std::unique_ptr<Driver> driver( Driver::createDriver( context, "sqlite" ) );
20✔
477
      if ( !driver )
20✔
478
        throw GeoDiffException( "Unable to use driver: sqlite" );
×
479
      driver->open( conn );
20✔
480
    }
20✔
481

482
    TmpFile changeset_BASE_MODIFIED( std::string( changeset ) + "_BASE_MODIFIED" );
20✔
483
    int rc = GEODIFF_createChangeset( contextHandle, base, modified, changeset_BASE_MODIFIED.c_path() );
20✔
484
    if ( rc != GEODIFF_SUCCESS )
20✔
485
      return rc;
×
486

487
    return GEODIFF_createRebasedChangesetEx( contextHandle, "sqlite", "", base, changeset_BASE_MODIFIED.c_path(), changeset_their, changeset, conflictfile );
20✔
488
  }
20✔
489
  catch ( const  GeoDiffException &exc )
×
490
  {
NEW
491
    return handleException( context, exc );
×
492
  }
×
493
}
494

495
static void createRebasedChangesetEx(
64✔
496
  Context *context,
497
  const char *driverName,
498
  const char * /* driverExtraInfo */,
499
  const char *base,
500
  const char *base2modified,
501
  const char *base2their,
502
  const char *rebased,
503
  const char *conflictfile )
504
{
505
  if ( !driverName || !base || !base2modified || !base2their || !rebased || !conflictfile )
64✔
506
  {
507
    throw GeoDiffException( "NULL arguments to GEODIFF_createRebasedChangesetEx" );
3✔
508
  }
509

510
  // TODO: use driverName + driverExtraInfo + base when creating rebased
511
  // changeset (e.g. to check whether a newly created ID is actually free)
512

513
  std::vector<ConflictFeature> conflicts;
63✔
514
  rebase( context, base2their, rebased, base2modified, conflicts );
318✔
515

516
  // output conflicts
517
  if ( conflicts.empty() )
60✔
518
  {
519
    context->logger().debug( "No conflicts present" );
162✔
520
  }
521
  else
522
  {
523
    nlohmann::json res = conflictsToJSON( conflicts );
6✔
524
    flushString( conflictfile, res.dump( 2 ) );
18✔
525
  }
6✔
526
}
63✔
527

528
int GEODIFF_createRebasedChangesetEx(
30✔
529
  GEODIFF_ContextH contextHandle,
530
  const char *driverName,
531
  const char *driverExtraInfo,
532
  const char *base,
533
  const char *base2modified,
534
  const char *base2their,
535
  const char *rebased,
536
  const char *conflictfile )
537
{
538
  Context *context = static_cast<Context *>( contextHandle );
30✔
539
  if ( !context )
30✔
540
  {
541
    return GEODIFF_ERROR;
1✔
542
  }
543

544
  try
545
  {
546
    createRebasedChangesetEx( context, driverName, driverExtraInfo, base, base2modified, base2their, rebased, conflictfile );
29✔
547
    return GEODIFF_SUCCESS;
26✔
548
  }
549
  catch ( const GeoDiffException &exc )
3✔
550
  {
551
    return handleException( context, exc );
3✔
552
  }
3✔
553
}
554

555

556
int GEODIFF_hasChanges(
171✔
557
  GEODIFF_ContextH contextHandle,
558
  const char *changeset )
559
{
560
  Context *context = static_cast<Context *>( contextHandle );
171✔
561
  if ( !context )
171✔
562
  {
563
    return -1;
1✔
564
  }
565

566
  if ( !changeset )
170✔
567
  {
568
    setAndLogError( context, "NULL arguments to GEODIFF_hasChanges" );
1✔
569
    return -1;
1✔
570
  }
571

572
  ChangesetReader reader;
169✔
573
  if ( !reader.open( changeset ) )
338✔
574
  {
575
    setAndLogError( context, "Could not open changeset: " + std::string( changeset ) );
2✔
576
    return -1;
2✔
577
  }
578

579
  return !reader.isEmpty();
167✔
580
}
169✔
581

582
int GEODIFF_changesCount(
80✔
583
  GEODIFF_ContextH contextHandle,
584
  const char *changeset )
585
{
586
  Context *context = static_cast<Context *>( contextHandle );
80✔
587
  if ( !context )
80✔
588
  {
589
    return -1;
1✔
590
  }
591

592
  if ( !changeset )
79✔
593
  {
594
    setAndLogError( context, "NULL arguments to GEODIFF_changesCount" );
1✔
595
    return -1;
1✔
596
  }
597

598
  ChangesetReader reader;
78✔
599
  if ( !reader.open( changeset ) )
156✔
600
  {
NEW
601
    setAndLogError( context, "Could not open changeset: " + std::string( changeset ) );
×
602
    return -1;
×
603
  }
604

605
  int changesCount = 0;
78✔
606
  ChangesetEntry entry;
78✔
607
  while ( reader.nextEntry( entry ) )
245✔
608
    ++changesCount;
167✔
609

610
  return changesCount;
78✔
611
}
78✔
612

613
static int listChangesJSON( Context *context, const char *changeset, const char *jsonfile, bool onlySummary )
111✔
614
{
615
  if ( !changeset )
111✔
616
  {
617
    setAndLogError( context, "Not provided changeset file to listChangeset" );
2✔
618
    return GEODIFF_ERROR;
2✔
619
  }
620

621
  ChangesetReader reader;
109✔
622
  if ( !reader.open( changeset ) )
218✔
623
  {
624
    setAndLogError( context, "Could not open changeset: " + std::string( changeset ) );
2✔
625
    return GEODIFF_ERROR;
2✔
626
  }
627

628
  nlohmann::json res;
107✔
629
  try
630
  {
631
    if ( onlySummary )
107✔
632
      res = changesetToJSONSummary( reader );
52✔
633
    else
634
      res = changesetToJSON( reader );
55✔
635
  }
NEW
636
  catch ( const GeoDiffException &exc )
×
637
  {
NEW
638
    return handleException( context, exc );
×
639
  }
×
640

641
  if ( !jsonfile )
107✔
642
  {
643
    context->logger().info( res.dump( 2 ) );
44✔
644
  }
645
  else
646
  {
647
    flushString( jsonfile, res.dump( 2 ) );
189✔
648
  }
649

650
  return GEODIFF_SUCCESS;
107✔
651
}
109✔
652

653
int GEODIFF_listChanges(
58✔
654
  GEODIFF_ContextH contextHandle,
655
  const char *changeset,
656
  const char *jsonfile )
657
{
658
  Context *context = static_cast<Context *>( contextHandle );
58✔
659
  if ( !context )
58✔
660
  {
661
    return GEODIFF_ERROR;
1✔
662
  }
663
  return listChangesJSON( context, changeset, jsonfile, false );
57✔
664
}
665

666
int GEODIFF_listChangesSummary( GEODIFF_ContextH contextHandle, const char *changeset, const char *jsonfile )
55✔
667
{
668
  Context *context = static_cast<Context *>( contextHandle );
55✔
669
  if ( !context )
55✔
670
  {
671
    return GEODIFF_ERROR;
1✔
672
  }
673

674
  return listChangesJSON( context, changeset, jsonfile, true );
54✔
675
}
676

677
static void invertChangesetByPath( const char *changeset, const char *changeset_inv )
49✔
678
{
679
  if ( !changeset )
49✔
680
  {
681
    throw GeoDiffException( "NULL arguments to GEODIFF_invertChangeset" );
3✔
682
  }
683

684
  if ( !fileexists( changeset ) )
96✔
685
  {
686
    throw GeoDiffException( "Missing input files in GEODIFF_invertChangeset: " + std::string( changeset ) );
3✔
687
  }
688

689
  ChangesetReader reader;
47✔
690
  if ( !reader.open( changeset ) )
94✔
691
  {
NEW
692
    throw GeoDiffException( "Could not open changeset: " + std::string( changeset ) );
×
693
  }
694

695
  ChangesetWriter writer;
47✔
696
  writer.open( changeset_inv );
47✔
697

698
  invertChangeset( reader, writer );
47✔
699
}
47✔
700

701
int GEODIFF_invertChangeset( GEODIFF_ContextH contextHandle, const char *changeset, const char *changeset_inv )
16✔
702
{
703
  Context *context = static_cast<Context *>( contextHandle );
16✔
704
  if ( !context )
16✔
705
  {
706
    return GEODIFF_ERROR;
1✔
707
  }
708

709
  try
710
  {
711
    invertChangesetByPath( changeset, changeset_inv );
15✔
712
  }
713
  catch ( const GeoDiffException &exc )
2✔
714
  {
715
    return handleException( context, exc );
2✔
716
  }
2✔
717

718
  return GEODIFF_SUCCESS;
13✔
719
}
720

721

722
int GEODIFF_concatChanges(
19✔
723
  GEODIFF_ContextH contextHandle,
724
  int inputChangesetsCount,
725
  const char **inputChangesets,
726
  const char *outputChangeset )
727
{
728
  Context *context = static_cast<Context *>( contextHandle );
19✔
729
  if ( !context )
19✔
730
  {
731
    return GEODIFF_ERROR;
1✔
732
  }
733

734
  if ( inputChangesetsCount < 2 )
18✔
735
  {
736
    setAndLogError( context, "Need at least two input changesets in GEODIFF_concatChanges" );
1✔
737
    return GEODIFF_ERROR;
1✔
738
  }
739

740
  if ( !inputChangesets || !outputChangeset )
17✔
741
  {
742
    setAndLogError( context, "NULL arguments to GEODIFF_concatChanges" );
1✔
743
    return GEODIFF_ERROR;
1✔
744
  }
745

746
  std::vector<std::string> inputFiles;
16✔
747
  for ( int i = 0; i < inputChangesetsCount; ++i )
49✔
748
  {
749
    std::string filename = inputChangesets[i];
34✔
750
    if ( !fileexists( filename ) )
34✔
751
    {
752
      setAndLogError( context, "Input file in GEODIFF_concatChanges does not exist: " + filename );
1✔
753
      return GEODIFF_ERROR;
1✔
754
    }
755
    inputFiles.push_back( filename );
33✔
756
  }
34✔
757

758
  try
759
  {
760
    concatChangesets( context, inputFiles, outputChangeset );
15✔
761
  }
NEW
762
  catch ( const GeoDiffException &exc )
×
763
  {
NEW
764
    return handleException( context, exc );
×
765
  }
×
766

767
  return GEODIFF_SUCCESS;
15✔
768
}
16✔
769

770

771
int GEODIFF_rebase(
45✔
772
  GEODIFF_ContextH contextHandle,
773
  const char *base,
774
  const char *modified_their,
775
  const char *modified,
776
  const char *conflictfile )
777
{
778
  Context *context = static_cast<Context *>( contextHandle );
45✔
779
  if ( !context )
45✔
780
  {
781
    return GEODIFF_ERROR;
1✔
782
  }
783

784
  if ( !base || !modified_their || !modified || !conflictfile )
44✔
785
  {
786
    setAndLogError( context, "NULL arguments to GEODIFF_rebase" );
1✔
787
    return GEODIFF_ERROR;
1✔
788
  }
789

790
  if ( !fileexists( base ) )
86✔
791
  {
792
    setAndLogError( context, std::string( "Missing 'base' file in GEODIFF_rebase: " ) + base );
1✔
793
    return GEODIFF_ERROR;
1✔
794
  }
795

796
  if ( !fileexists( modified_their ) )
84✔
797
  {
798
    setAndLogError( context, std::string( "Missing 'modified_their' file in GEODIFF_rebase: " ) + modified_their );
1✔
799
    return GEODIFF_ERROR;
1✔
800
  }
801

802
  if ( !fileexists( modified ) )
82✔
803
  {
804
    setAndLogError( context, std::string( "Missing 'modified' file in GEODIFF_rebase: " ) + modified );
1✔
805
    return GEODIFF_ERROR;
1✔
806
  }
807

808
  std::string root = std::string( modified );
40✔
809

810
  TmpFile base2theirs( root + "_base2theirs.bin" );
40✔
811
  try
812
  {
813
    createChangesetEx( context, "sqlite", nullptr, base, modified_their, base2theirs.c_path() );
40✔
814
  }
NEW
815
  catch ( GeoDiffException &exc )
×
816
  {
NEW
817
    exc.addContext( "Unable to perform GEODIFF_createChangeset base2theirs" );
×
NEW
818
    return handleException( context, exc );
×
819
  }
×
820

821
  return GEODIFF_rebaseEx( contextHandle, "sqlite", "", base, modified, base2theirs.c_path(), conflictfile );
40✔
822
}
40✔
823

824

825
int GEODIFF_rebaseEx(
48✔
826
  GEODIFF_ContextH contextHandle,
827
  const char *driverName,
828
  const char *driverExtraInfo,
829
  const char *base,
830
  const char *modified,
831
  const char *base2their,
832
  const char *conflictfile )
833
{
834
  Context *context = static_cast<Context *>( contextHandle );
48✔
835
  if ( !context )
48✔
836
  {
837
    return GEODIFF_ERROR;
1✔
838
  }
839

840
  if ( !base || !modified || !base2their || !conflictfile )
47✔
841
  {
842
    setAndLogError( context, "NULL arguments to GEODIFF_rebase" );
1✔
843
    return GEODIFF_ERROR;
1✔
844
  }
845

846
  try
847
  {
848
    std::string root = tmpdir( ) + "geodiff_" + randomString( 6 );
46✔
849

850
    // situation 1: base2theirs is null, so we do not need rebase. modified is already fine
851
    if ( !GEODIFF_hasChanges( contextHandle, base2their ) )
46✔
852
    {
853
      return GEODIFF_SUCCESS;
7✔
854
    }
855

856
    TmpFile base2modified( root + "_base2modified.bin" );
39✔
857
    try
858
    {
859
      createChangesetEx( context, driverName, driverExtraInfo, base, modified, base2modified.c_path() );
39✔
860
    }
861
    catch ( GeoDiffException &exc )
2✔
862
    {
863
      exc.addContext( "Unable to perform GEODIFF_createChangeset base2modified" );
2✔
864
      return handleException( context, exc );
2✔
865
    }
2✔
866

867
    // situation 2: we do not have changes (modified == base), so result is modified_theirs
868
    if ( !GEODIFF_hasChanges( contextHandle, base2modified.c_path() ) )
37✔
869
    {
870
      try
871
      {
872
        applyChangesetEx( context, driverName, driverExtraInfo, modified, base2their );
2✔
873
      }
874
      catch ( GeoDiffException &exc )
1✔
875
      {
876
        exc.addContext( "Unable to perform GEODIFF_applyChangeset base2theirs" );
1✔
877
        return handleException( context, exc );
1✔
878
      }
1✔
879

880
      return GEODIFF_SUCCESS;
1✔
881
    }
882

883
    // situation 3: we have changes both in ours and theirs
884

885
    // 3A) Create all changesets
886
    TmpFile theirs2final( root + "_theirs2final.bin" );
35✔
887
    try
888
    {
889
      createRebasedChangesetEx( context, driverName, driverExtraInfo, base,
35✔
890
                                base2modified.c_path(), base2their,
891
                                theirs2final.c_path(), conflictfile );
892
    }
893
    catch ( GeoDiffException &exc )
1✔
894
    {
895
      exc.addContext( "Unable to perform GEODIFF_createChangeset theirs2final" );
1✔
896
      return handleException( context, exc );
1✔
897
    }
1✔
898

899
    TmpFile modified2base( root + "_modified2base.bin" );
34✔
900
    try
901
    {
902
      invertChangesetByPath( base2modified.c_path(), modified2base.c_path() );
34✔
903
    }
NEW
904
    catch ( GeoDiffException &exc )
×
905
    {
NEW
906
      exc.addContext( "Unable to perform GEODIFF_invertChangeset modified2base" );
×
NEW
907
      return handleException( context, exc );
×
908
    }
×
909

910
    // 3B) concat to single changeset
911
    TmpFile modified2final( root + "_modified2final.bin" );
34✔
912
    std::vector<std::string> concatInput;
34✔
913
    concatInput.push_back( modified2base.path() );
34✔
914
    concatInput.push_back( base2their );
34✔
915
    concatInput.push_back( theirs2final.path() );
34✔
916
    concatChangesets( context, concatInput, modified2final.path() );  // throws GeoDiffException exception on error
34✔
917

918
    // 3C) apply at once
919
    try
920
    {
921
      applyChangesetEx( context, driverName, driverExtraInfo, modified, modified2final.c_path() );
34✔
922
    }
923
    catch ( GeoDiffException &exc )
4✔
924
    {
925
      exc.addContext( "Unable to perform GEODIFF_applyChangeset modified2final" );
4✔
926
      return handleException( context, exc );
4✔
927
    }
4✔
928

929
    return GEODIFF_SUCCESS;
30✔
930
  }
46✔
NEW
931
  catch ( const GeoDiffException &exc )
×
932
  {
NEW
933
    return handleException( context, exc );
×
934
  }
×
935
}
936

937

938
int GEODIFF_makeCopy( GEODIFF_ContextH contextHandle,
25✔
939
                      const char *driverSrcName,
940
                      const char *driverSrcExtraInfo,
941
                      const char *src,
942
                      const char *driverDstName,
943
                      const char *driverDstExtraInfo,
944
                      const char *dst )
945
{
946
  Context *context = static_cast<Context *>( contextHandle );
25✔
947
  if ( !context )
25✔
948
  {
949
    return GEODIFF_ERROR;
1✔
950
  }
951

952
  try
953
  {
954
    makeCopy( context,
24✔
955
              driverSrcName,
956
              driverSrcExtraInfo,
957
              src,
958
              driverDstName,
959
              driverDstExtraInfo,
960
              dst );
961
  }
962
  catch ( const GeoDiffException &exc )
3✔
963
  {
964
    return handleException( context, exc );
3✔
965
  }
3✔
966

967
  return GEODIFF_SUCCESS;
21✔
968
}
969

970
int GEODIFF_makeCopySqlite( GEODIFF_ContextH contextHandle, const char *src, const char *dst )
22✔
971
{
972
  Context *context = static_cast<Context *>( contextHandle );
22✔
973
  if ( !context )
22✔
974
  {
975
    return GEODIFF_ERROR;
1✔
976
  }
977

978
  if ( !src || !dst )
21✔
979
  {
980
    setAndLogError( context, "NULL arguments to GEODIFF_makeCopySqlite" );
3✔
981
    return GEODIFF_ERROR;
3✔
982
  }
983

984
  if ( !fileexists( src ) )
36✔
985
  {
986
    setAndLogError( context, "MakeCopySqlite: Source database does not exist: " + std::string( src ) );
2✔
987
    return GEODIFF_ERROR;
2✔
988
  }
989

990
  // If the destination file already exists, let's replace it. This is for convenience: if the file exists
991
  // and it is SQLite database, the backup API would overwrite it, but if the file would not be a valid
992
  // SQLite database, it would fail to open. With this check+remove we make sure that any existing file
993
  // gets overwritten, regardless of its original content (making the API easier to use for the caller).
994
  if ( fileexists( dst ) )
32✔
995
  {
996
    if ( fileremove( dst ) )
4✔
997
    {
998
      context->logger().warn( "MakeCopySqlite: Removed existing destination database: " + std::string( dst ) );
6✔
999
    }
1000
    else
1001
    {
1002
      context->logger().error( "MakeCopySqlite: Failed to remove existing destination database: " + std::string( dst ) );
×
1003
    }
1004
  }
1005

1006
  Sqlite3Db dbFrom, dbTo;
16✔
1007
  try
1008
  {
1009
    dbFrom.open( src );
32✔
1010
  }
NEW
1011
  catch ( const GeoDiffException &e )
×
1012
  {
NEW
1013
    setAndLogError( context, "MakeCopySqlite: Unable to open source database: " + std::string( src ) + "\n" + e.what() );
×
NEW
1014
    return e.errorCode();
×
1015
  }
×
1016

1017
  try
1018
  {
1019
    dbTo.create( dst );
16✔
1020
  }
NEW
1021
  catch ( const GeoDiffException &e )
×
1022
  {
NEW
1023
    setAndLogError( context, "MakeCopySqlite: Unable to open destination database: " + std::string( dst ) + "\n" + e.what() );
×
NEW
1024
    return e.errorCode();
×
1025
  }
×
1026

1027
  // Set up the backup procedure to copy from the "main" database of
1028
  // connection pFrom to the main database of connection pTo.
1029
  // If something goes wrong, pBackup will be set to NULL and an error
1030
  // code and message left in connection pTo.
1031

1032
  // If the backup object is successfully created, call backup_step()
1033
  // to copy data from pFrom to pTo. Then call backup_finish()
1034
  // to release resources associated with the pBackup object.  If an
1035
  // error occurred, then an error code and message will be left in
1036
  // connection pTo. If no error occurred, then the error code belonging
1037
  // to pTo is set to SQLITE_OK.
1038

1039
  sqlite3_backup *pBackup = sqlite3_backup_init( dbTo.get(), "main", dbFrom.get(), "main" );
16✔
1040
  if ( pBackup )
16✔
1041
  {
1042
    sqlite3_backup_step( pBackup, -1 );
16✔
1043
    sqlite3_backup_finish( pBackup );
16✔
1044
  }
1045

1046
  std::string errorMsg;
16✔
1047
  if ( sqlite3_errcode( dbTo.get() ) )
16✔
1048
    errorMsg = sqlite3_errmsg( dbTo.get() );
×
1049

1050
  if ( !errorMsg.empty() )
16✔
1051
  {
NEW
1052
    setAndLogError( context, "MakeCopySqlite: backup failed: " + errorMsg );
×
1053
    return GEODIFF_ERROR;
×
1054
  }
1055

1056
  return GEODIFF_SUCCESS;
16✔
1057
}
16✔
1058

1059

1060
int GEODIFF_dumpData( GEODIFF_ContextH contextHandle, const char *driverName, const char *driverExtraInfo, const char *src, const char *changeset )
5✔
1061
{
1062
  Context *context = static_cast<Context *>( contextHandle );
5✔
1063
  if ( !context )
5✔
1064
  {
1065
    return GEODIFF_ERROR;
1✔
1066
  }
1067

1068
  if ( !driverName || !src || !changeset )
4✔
1069
  {
1070
    setAndLogError( context, "NULL arguments to GEODIFF_dumpData" );
1✔
1071
    return GEODIFF_ERROR;
1✔
1072
  }
1073

1074
  std::unique_ptr<Driver> driver( Driver::createDriver( context,  std::string( driverName ) ) );
3✔
1075
  if ( !driver )
3✔
1076
  {
1077
    setAndLogError( context, "Cannot create driver " + std::string( driverName ) );
1✔
1078
    return GEODIFF_ERROR;
1✔
1079
  }
1080

1081
  try
1082
  {
1083
    // open source
1084
    std::map<std::string, std::string> conn;
2✔
1085
    conn["base"] = std::string( src );
6✔
1086
    if ( driverExtraInfo )
2✔
1087
      conn["conninfo"] = std::string( driverExtraInfo );
8✔
1088
    driver->open( conn );
2✔
1089

1090
    // get source data
1091
    ChangesetWriter writer;
2✔
1092
    writer.open( changeset );
2✔
1093
    driver->dumpData( writer );
2✔
1094
  }
2✔
NEW
1095
  catch ( const GeoDiffException &exc )
×
1096
  {
NEW
1097
    return handleException( context, exc );
×
1098
  }
×
1099

1100
  return GEODIFF_SUCCESS;
2✔
1101
}
3✔
1102

1103

1104
int GEODIFF_schema( GEODIFF_ContextH contextHandle, const char *driverName, const char *driverExtraInfo, const char *src, const char *json )
10✔
1105
{
1106
  Context *context = static_cast<Context *>( contextHandle );
10✔
1107
  if ( !context )
10✔
1108
  {
1109
    return GEODIFF_ERROR;
1✔
1110
  }
1111

1112
  if ( !driverName || !src || !json )
9✔
1113
  {
1114
    setAndLogError( context, "NULL arguments to GEODIFF_schema" );
1✔
1115
    return GEODIFF_ERROR;
1✔
1116
  }
1117

1118
  std::unique_ptr<Driver> driver( Driver::createDriver( context,  std::string( driverName ) ) );
8✔
1119
  if ( !driver )
8✔
1120
  {
1121
    setAndLogError( context, "Cannot create driver " + std::string( driverName ) );
1✔
1122
    return GEODIFF_ERROR;
1✔
1123
  }
1124

1125
  try
1126
  {
1127
    // open source
1128
    std::map<std::string, std::string> conn;
7✔
1129
    conn["base"] = std::string( src );
21✔
1130
    if ( driverExtraInfo )
7✔
1131
      conn["conninfo"] = std::string( driverExtraInfo );
16✔
1132
    driver->open( conn );
7✔
1133

1134
    // prepare JSON
1135
    auto tablesData = nlohmann::json::array();
5✔
1136
    for ( const std::string &tableName : driver->listTables() )
9✔
1137
    {
1138
      const TableSchema tbl = driver->tableSchema( tableName );
4✔
1139

1140
      auto columnsJson = nlohmann::json::array();
4✔
1141
      for ( const TableColumnInfo &column : tbl.columns )
20✔
1142
      {
1143
        nlohmann::json columnData;
16✔
1144
        columnData[ "name" ] = column.name;
16✔
1145
        columnData[ "type" ] = TableColumnType::baseTypeToString( column.type.baseType );
16✔
1146
        columnData[ "type_db" ] = column.type.dbType;
16✔
1147
        if ( column.isPrimaryKey )
16✔
1148
          columnData[ "primary_key" ] = true;
4✔
1149
        if ( column.isNotNull )
16✔
1150
          columnData[ "not_null" ] = true;
4✔
1151
        if ( column.isAutoIncrement )
16✔
1152
          columnData[ "auto_increment" ] = true;
4✔
1153
        if ( column.isGeometry )
16✔
1154
        {
1155
          nlohmann::json geometryData;
4✔
1156
          geometryData[ "type" ] = column.geomType;
4✔
1157
          geometryData[ "srs_id" ] = std::to_string( column.geomSrsId );
4✔
1158
          if ( column.geomHasZ )
4✔
1159
            geometryData[ "has_z" ] = true;
×
1160
          if ( column.geomHasM )
4✔
1161
            geometryData[ "has_m" ] = true;
×
1162

1163
          columnData[ "geometry" ] = geometryData;
4✔
1164
        }
4✔
1165

1166
        columnsJson.push_back( columnData );
16✔
1167
      }
16✔
1168

1169
      nlohmann::json tableJson;
4✔
1170
      tableJson[ "table"] = tableName;
4✔
1171
      tableJson[ "columns" ] = columnsJson;
4✔
1172
      if ( tbl.crs.srsId != 0 )
4✔
1173
      {
1174
        nlohmann::json crsJson;
4✔
1175
        crsJson[ "srs_id" ] = tbl.crs.srsId;
4✔
1176
        crsJson[ "auth_name" ] = tbl.crs.authName;
4✔
1177
        crsJson[ "auth_code" ] = tbl.crs.authCode;
4✔
1178
        crsJson[ "wkt" ] = tbl.crs.wkt;
4✔
1179

1180
        tableJson[ "crs" ] = crsJson;
4✔
1181
      }
4✔
1182
      tablesData.push_back( tableJson );
4✔
1183
    }
9✔
1184

1185
    nlohmann::json res;
5✔
1186
    res[ "geodiff_schema" ] = tablesData;
5✔
1187

1188
    // write file content
1189
    flushString( json, res.dump( 2 ) );
15✔
1190
  }
7✔
1191
  catch ( const GeoDiffException &exc )
2✔
1192
  {
1193
    return handleException( context, exc );
2✔
1194
  }
2✔
1195

1196
  return GEODIFF_SUCCESS;
5✔
1197
}
8✔
1198

1199

1200
GEODIFF_ChangesetReaderH GEODIFF_readChangeset( GEODIFF_ContextH contextHandle, const char *changeset )
8✔
1201
{
1202
  Context *context = static_cast<Context *>( contextHandle );
8✔
1203
  if ( !context )
8✔
1204
  {
1205
    return nullptr;
1✔
1206
  }
1207

1208
  if ( !changeset )
7✔
1209
  {
1210
    setAndLogError( context, "NULL changeset argument to GEODIFF_readChangeset" );
1✔
1211
    return nullptr;
1✔
1212
  }
1213

1214
  ChangesetReader *reader = new ChangesetReader;
6✔
1215
  if ( !reader->open( changeset ) )
18✔
1216
  {
1217
    delete reader;
1✔
1218
    return nullptr;
1✔
1219
  }
1220
  return reader;
5✔
1221
}
1222

1223
GEODIFF_ChangesetEntryH GEODIFF_CR_nextEntry( GEODIFF_ContextH contextHandle, GEODIFF_ChangesetReaderH readerHandle, bool *ok )
14✔
1224
{
1225
  if ( !ok )
14✔
1226
  {
1227
    return nullptr;
1✔
1228
  }
1229

1230
  Context *context = static_cast<Context *>( contextHandle );
13✔
1231
  if ( !context )
13✔
1232
  {
1233
    *ok = false;
1✔
1234
    return nullptr;
1✔
1235
  }
1236

1237
  *ok = true;
12✔
1238
  ChangesetReader *reader = static_cast<ChangesetReader *>( readerHandle );
12✔
1239
  if ( !reader )
12✔
1240
  {
1241
    *ok = false;
1✔
1242
    return nullptr;
1✔
1243
  }
1244

1245
  std::unique_ptr<ChangesetEntry> entry( new ChangesetEntry );
11✔
1246
  try
1247
  {
1248
    if ( !reader->nextEntry( *entry ) )
11✔
1249
    {
1250
      // we have reached the end of file
1251
      return nullptr;
4✔
1252
    }
1253
  }
1254
  catch ( const GeoDiffException &exc )
1✔
1255
  {
1256
    setAndLogError( context, exc.what() );
1✔
1257
    *ok = false;
1✔
1258
    return nullptr;
1✔
1259
  }
1✔
1260
  return entry.release();
6✔
1261
}
11✔
1262

1263
void GEODIFF_CR_destroy( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetReaderH readerHandle )
5✔
1264
{
1265
  delete static_cast<ChangesetReader *>( readerHandle );
5✔
1266
}
5✔
1267

1268
int GEODIFF_CE_operation( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetEntryH entryHandle )
6✔
1269
{
1270
  return static_cast<ChangesetEntry *>( entryHandle )->op;
6✔
1271
}
1272

1273
GEODIFF_ChangesetTableH GEODIFF_CE_table( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetEntryH entryHandle )
6✔
1274
{
1275
  ChangesetTable *table = static_cast<ChangesetEntry *>( entryHandle )->table;
6✔
1276
  return table;
6✔
1277
}
1278

1279
int GEODIFF_CE_countValues( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetEntryH entryHandle )
6✔
1280
{
1281
  ChangesetEntry *entry = static_cast<ChangesetEntry *>( entryHandle );
6✔
1282
  size_t ret = entry->op == ChangesetEntry::OpDelete ? entry->oldValues.size() : entry->newValues.size();
6✔
1283
  return ( int ) ret;
6✔
1284
}
1285

1286
GEODIFF_ValueH GEODIFF_CE_oldValue( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetEntryH entryHandle, int i )
32✔
1287
{
1288
  return new Value( static_cast<ChangesetEntry *>( entryHandle )->oldValues[i] );
32✔
1289
}
1290

1291
GEODIFF_ValueH GEODIFF_CE_newValue( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetEntryH entryHandle, int i )
32✔
1292
{
1293
  return new Value( static_cast<ChangesetEntry *>( entryHandle )->newValues[i] );
32✔
1294
}
1295

1296
void GEODIFF_CE_destroy( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetEntryH entryHandle )
6✔
1297
{
1298
  delete static_cast<ChangesetEntry *>( entryHandle );
6✔
1299
}
6✔
1300

1301
int GEODIFF_V_type( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle )
64✔
1302
{
1303
  return static_cast<Value *>( valueHandle )->type();
64✔
1304
}
1305

1306
int64_t GEODIFF_V_getInt( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle )
6✔
1307
{
1308
  return static_cast<Value *>( valueHandle )->getInt();
6✔
1309
}
1310

1311
double GEODIFF_V_getDouble( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle )
4✔
1312
{
1313
  return static_cast<Value *>( valueHandle )->getDouble();
4✔
1314
}
1315

1316
void GEODIFF_V_destroy( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle )
64✔
1317
{
1318
  delete static_cast<Value *>( valueHandle );
64✔
1319
}
64✔
1320

1321
int GEODIFF_V_getDataSize( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle )
14✔
1322
{
1323
  size_t ret = static_cast<Value *>( valueHandle )->getString().size();
14✔
1324
  return ( int ) ret;
14✔
1325
}
1326

1327
void GEODIFF_V_getData( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle, char *data )
14✔
1328
{
1329
  const std::string &str = static_cast<Value *>( valueHandle )->getString();
14✔
1330
  memcpy( data, str.data(), str.size() );
14✔
1331
}
14✔
1332

1333
const char *GEODIFF_CT_name( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetTableH tableHandle )
6✔
1334
{
1335
  return static_cast<ChangesetTable *>( tableHandle )->name.data();
6✔
1336
}
1337

1338
int GEODIFF_CT_columnCount( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetTableH tableHandle )
6✔
1339
{
1340
  size_t ret = static_cast<ChangesetTable *>( tableHandle )->columnCount();
6✔
1341
  return ( int ) ret;
6✔
1342
}
1343

1344
bool GEODIFF_CT_columnIsPkey( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetTableH tableHandle, int i )
36✔
1345
{
1346
  return static_cast<ChangesetTable *>( tableHandle )->primaryKeys.at( i );
36✔
1347
}
1348

1349
int GEODIFF_createWkbFromGpkgHeader( GEODIFF_ContextH contextHandle, const char *gpkgWkb, size_t gpkgLength, const char **wkb, size_t *wkbLength )
8✔
1350
{
1351
  const Context *context = static_cast<const Context *>( contextHandle );
8✔
1352
  if ( !context || !gpkgWkb || !wkb || !wkbLength )
8✔
1353
  {
1354
    return GEODIFF_ERROR;
6✔
1355
  }
1356

1357
  if ( gpkgLength == 0 )
2✔
1358
  {
1359
    return GEODIFF_ERROR;
×
1360
  }
1361

1362
  std::string gpkgWkbStr( gpkgWkb, gpkgLength );
2✔
1363
  int headerSize = parseGpkgbHeaderSize( gpkgWkbStr );
2✔
1364

1365
  size_t result_len = gpkgLength - headerSize;
2✔
1366
  *wkb = gpkgWkb + headerSize;
2✔
1367
  *wkbLength = result_len;
2✔
1368

1369
  return GEODIFF_SUCCESS;
2✔
1370
}
2✔
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