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

MerginMaps / geodiff / 30617331136

31 Jul 2026 08:43AM UTC coverage: 87.111% (-0.5%) from 87.592%
30617331136

Pull #252

github

web-flow
Merge 2bb04d691 into e71dfe1a2
Pull Request #252: Allow schema changes in diffs

1325 of 1491 new or added lines in 13 files covered. (88.87%)

15 existing lines in 4 files now uncovered.

4481 of 5144 relevant lines covered (87.11%)

679.06 hits per line

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

91.75
/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 <variant>
31
#include <vector>
32

33
#include "json.hpp"
34

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

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

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

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

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

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

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

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

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

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

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

108
GEODIFF_ContextH GEODIFF_createContext()
159✔
109
{
110
  Context *context = new Context();
159✔
111

112
  sqlite3_initialize();
159✔
113

114
  return ( GEODIFF_ContextH ) context;
159✔
115
}
116

117
int GEODIFF_CX_setLoggerCallback( GEODIFF_ContextH contextHandle, GEODIFF_LoggerCallback loggerCallback )
36✔
118
{
119

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

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

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

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

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

162
  try
163
  {
164
    context->setTablesToSkip( tables );
6✔
165
  }
166
  catch ( const GeoDiffException &exc )
1✔
167
  {
168
    return handleException( context, exc );
1✔
169
  }
1✔
170
  return GEODIFF_SUCCESS;
5✔
171
}
6✔
172

173
int GEODIFF_CX_setTablesToInclude( GEODIFF_ContextH contextHandle, int tablesCount, const char **tablesToInclude )
8✔
174
{
175
  Context *context = static_cast<Context *>( contextHandle );
8✔
176
  if ( !context )
8✔
177
  {
178
    return GEODIFF_ERROR;
1✔
179
  }
180

181
  if ( tablesCount > 0 && !tablesToInclude )
7✔
182
  {
183
    setAndLogError( context, "NULL arguments to GEODIFF_CX_setTablesToInclude" );
1✔
184
    return GEODIFF_ERROR;
1✔
185
  }
186

187
  std::vector<std::string> tables;
6✔
188
  for ( int i = 0; i < tablesCount; ++i )
10✔
189
  {
190
    tables.push_back( tablesToInclude[i] );
8✔
191
  }
192

193
  try
194
  {
195
    context->setTablesToInclude( tables );
6✔
196
  }
197
  catch ( const GeoDiffException &exc )
1✔
198
  {
199
    return handleException( context, exc );
1✔
200
  }
1✔
201
  return GEODIFF_SUCCESS;
5✔
202
}
6✔
203

204
const char *GEODIFF_CX_lastError( GEODIFF_ContextH contextHandle )
9✔
205
{
206
  const Context *context = static_cast<const Context *>( contextHandle );
9✔
207
  if ( !context )
9✔
208
    return "";
×
209
  return context->lastError().c_str();
9✔
210
}
211

212
void GEODIFF_CX_destroy( GEODIFF_ContextH contextHandle )
158✔
213
{
214
  Context *context = static_cast<Context *>( contextHandle );
158✔
215
  if ( context )
158✔
216
  {
217
    delete context;
158✔
218
    context = nullptr;
158✔
219
  }
220
}
158✔
221

222
int GEODIFF_createChangeset( GEODIFF_ContextH contextHandle, const char *base, const char *modified, const char *changeset )
161✔
223
{
224
  return GEODIFF_createChangesetEx( contextHandle, "sqlite", nullptr, base, modified, changeset );
161✔
225
}
226

227
int GEODIFF_applyChangeset( GEODIFF_ContextH contextHandle, const char *base, const char *changeset )
49✔
228
{
229
  return GEODIFF_applyChangesetEx( contextHandle, "sqlite", nullptr, base, changeset );
49✔
230
}
231

232
static void createChangesetEx( const Context *context, const char *driverName, const char *driverExtraInfo,
273✔
233
                               const char *base, const char *modified,
234
                               const char *changeset )
235
{
236
  if ( !driverName || !base || !modified || !changeset )
273✔
237
  {
238
    throw GeoDiffException( "NULL arguments to GEODIFF_createChangesetEx" );
3✔
239
  }
240

241
  std::map<std::string, std::string> conn;
272✔
242
  conn["base"] = std::string( base );
1,088✔
243
  conn["modified"] = std::string( modified );
816✔
244
  if ( driverExtraInfo )
272✔
245
    conn["conninfo"] = std::string( driverExtraInfo );
340✔
246
  std::unique_ptr<Driver> driver( Driver::createDriver( context, std::string( driverName ) ) );
272✔
247
  if ( !driver )
272✔
248
    throw GeoDiffException( "Unable to use driver: " + std::string( driverName ) );
3✔
249
  driver->open( conn );
271✔
250

251
  ChangesetWriter writer;
266✔
252
  writer.open( changeset );
266✔
253
  driver->createChangeset( writer );
266✔
254
}
284✔
255

256

257
int GEODIFF_createChangesetEx( GEODIFF_ContextH contextHandle, const char *driverName, const char *driverExtraInfo,
209✔
258
                               const char *base, const char *modified,
259
                               const char *changeset )
260
{
261
  Context *context = static_cast<Context *>( contextHandle );
209✔
262
  if ( !context )
209✔
263
  {
264
    return GEODIFF_ERROR;
1✔
265
  }
266

267
  try
268
  {
269
    createChangesetEx( context, driverName, driverExtraInfo, base, modified, changeset );
208✔
270
  }
271
  catch ( const GeoDiffException &exc )
8✔
272
  {
273
    return handleException( context, exc );
8✔
274
  }
8✔
275

276
  return GEODIFF_SUCCESS;
200✔
277
}
278

279

280
static void makeCopy( const Context *context,
29✔
281
                      const char *driverSrcName,
282
                      const char *driverSrcExtraInfo,
283
                      const char *src,
284
                      const char *driverDstName,
285
                      const char *driverDstExtraInfo,
286
                      const char *dst )
287
{
288
  if ( !driverSrcName || !driverSrcExtraInfo || !driverDstName || !driverDstExtraInfo || !src || !dst )
29✔
289
  {
290
    throw GeoDiffException( "NULL arguments to GEODIFF_makeCopy" );
3✔
291
  }
292

293
  std::string srcDriverName( driverSrcName );
56✔
294
  std::string dstDriverName( driverDstName );
28✔
295
  std::unique_ptr<Driver> driverSrc( Driver::createDriver( context, srcDriverName ) );
28✔
296
  if ( !driverSrc )
28✔
297
  {
298
    throw GeoDiffException( "Cannot create driver " + srcDriverName );
1✔
299
  }
300

301
  std::unique_ptr<Driver> driverDst( Driver::createDriver( context, dstDriverName ) );
27✔
302
  if ( !driverDst )
27✔
303
  {
304
    throw GeoDiffException( "Cannot create driver " + dstDriverName );
1✔
305
  }
306

307
  TmpFile tmpFileChangeset( tmpdir( ) + "geodiff_changeset" + std::to_string( rand() ) );
26✔
308

309
  // open source
310
  std::map<std::string, std::string> connSrc;
26✔
311
  connSrc["base"] = std::string( src );
104✔
312
  connSrc["conninfo"] = std::string( driverSrcExtraInfo );
78✔
313
  driverSrc->open( connSrc );
26✔
314

315
  // get source tables
316
  std::vector<TableSchema> tables;
26✔
317
  std::vector<std::string> tableNames = driverSrc->listTables();
26✔
318

319
  if ( srcDriverName != dstDriverName )
26✔
320
  {
321
    for ( const std::string &tableName : tableNames )
64✔
322
    {
323
      TableSchema tbl = driverSrc->tableSchema( tableName );
44✔
324
      tableSchemaConvert( driverDstName, tbl );
44✔
325
      tables.push_back( tbl );
44✔
326
    }
44✔
327
  }
328
  else
329
  {
330
    for ( const std::string &tableName : tableNames )
18✔
331
    {
332
      TableSchema tbl = driverSrc->tableSchema( tableName );
12✔
333
      tables.push_back( tbl );
12✔
334
    }
12✔
335
  }
336

337
  // get source data
338
  {
339
    ChangesetWriter writer;
26✔
340
    writer.open( tmpFileChangeset.c_path() );
26✔
341
    driverSrc->dumpData( writer );
26✔
342
  }
26✔
343

344
  // create destination
345
  std::map<std::string, std::string> connDst;
26✔
346
  connDst["base"] = dst;
78✔
347
  connDst["conninfo"] = std::string( driverDstExtraInfo );
78✔
348
  driverDst->create( connDst, true );
26✔
349

350
  // create tables in destination
351
  driverDst->createTables( tables );
26✔
352

353
  // insert data to destination
354
  {
355
    ChangesetReader reader;
26✔
356
    reader.open( tmpFileChangeset.c_path() );
26✔
357
    driverDst->applyChangeset( reader );
26✔
358
  }
26✔
359

360
  // TODO: add spatial index to tables with geometry columns?
361
}
33✔
362

363

364
int GEODIFF_createChangesetDr( GEODIFF_ContextH contextHandle, const char *driverSrcName, const char *driverSrcExtraInfo, const char *src,
14✔
365
                               const char *driverDstName, const char *driverDstExtraInfo, const char *dst,
366
                               const char *changeset )
367
{
368
  Context *context = static_cast<Context *>( contextHandle );
14✔
369
  if ( !context )
14✔
370
  {
371
    return GEODIFF_ERROR;
1✔
372
  }
373

374
  if ( !driverSrcName || !driverSrcExtraInfo || !driverDstName || !driverDstExtraInfo || !src || !dst || !changeset )
13✔
375
  {
376
    setAndLogError( context, "NULL arguments to GEODIFF_createChangesetAcrossDrivers" );
1✔
377
    return GEODIFF_ERROR;
1✔
378
  }
379

380
  // check both driver names and connection details, for more context
381
  // see https://github.com/MerginMaps/geodiff/issues/185
382
  if ( strcmp( driverSrcName, driverDstName ) == 0 && strcmp( driverSrcExtraInfo, driverDstExtraInfo ) == 0 )
12✔
383
  {
384
    return GEODIFF_createChangesetEx( contextHandle, driverSrcName, driverSrcExtraInfo, src, dst, changeset );
8✔
385
  }
386

387
  // copy both sources to geopackage and create changeset
388
  TmpFile tmpSrcGpkg;
4✔
389
  TmpFile tmpDstGpkg;
4✔
390

391
  if ( strcmp( driverSrcName, Driver::SQLITEDRIVERNAME.c_str() ) != 0 )
4✔
392
  {
393
    tmpSrcGpkg.setPath( tmpdir( ) + "_gpkg-" + randomString( 6 ) );
3✔
394
    try
395
    {
396
      makeCopy( context, driverSrcName, driverSrcExtraInfo, src, Driver::SQLITEDRIVERNAME.c_str(), "", tmpSrcGpkg.c_path() );
3✔
397
    }
398
    catch ( GeoDiffException &exc )
×
399
    {
400

401
      exc.addContext( "Failed to create a copy of base source for driver " + std::string( driverSrcName ) );
×
402
      return handleException( context, exc );
×
403
    }
×
404
  }
405

406
  if ( strcmp( driverDstName, Driver::SQLITEDRIVERNAME.c_str() ) != 0 )
4✔
407
  {
408
    tmpDstGpkg.setPath( tmpdir() + "_gpkg-" + randomString( 6 ) );
2✔
409
    try
410
    {
411
      makeCopy( context, driverDstName, driverDstExtraInfo, dst, Driver::SQLITEDRIVERNAME.c_str(), "", tmpDstGpkg.c_path() );
2✔
412
    }
413
    catch ( GeoDiffException &exc )
×
414
    {
415
      exc.addContext( "Failed to create a copy of modified source for driver " + std::string( driverDstName ) );
×
416
      return handleException( context, exc );
×
417
    }
×
418
  }
419

420
  return GEODIFF_createChangesetEx(
11✔
421
           contextHandle,
422
           Driver::SQLITEDRIVERNAME.c_str(),
423
           "",
424
           tmpSrcGpkg.path().empty() ? src : tmpSrcGpkg.c_path(),
7✔
425
           tmpDstGpkg.path().empty() ? dst : tmpDstGpkg.c_path(),
6✔
426
           changeset );
4✔
427
}
4✔
428

429

430
static void applyChangesetEx(
95✔
431
  Context *context,
432
  const char *driverName,
433
  const char *driverExtraInfo,
434
  const char *base,
435
  const char *changeset )
436
{
437
  if ( !driverName || !base || !changeset )
95✔
438
  {
439
    throw GeoDiffException( "NULL arguments to GEODIFF_applyChangesetEx" );
3✔
440
  }
441

442
  std::map<std::string, std::string> conn;
94✔
443
  conn["base"] = std::string( base );
282✔
444
  if ( driverExtraInfo )
94✔
445
    conn["conninfo"] = std::string( driverExtraInfo );
180✔
446
  std::unique_ptr<Driver> driver( Driver::createDriver( context,  std::string( driverName ) ) );
94✔
447
  if ( !driver )
94✔
448
    throw GeoDiffException( "Unable to use driver: " + std::string( driverName ) );
3✔
449
  driver->open( conn );
93✔
450

451
  ChangesetReader reader;
93✔
452
  if ( !reader.open( changeset ) )
186✔
453
    throw GeoDiffException( "Unable to open changeset file for reading: " + std::string( changeset ) );
3✔
454
  if ( reader.isEmpty() )
92✔
455
  {
456
    context->logger().debug( "--- no changes ---" );
12✔
457
    return;
6✔
458
  }
459

460
  driver->applyChangeset( reader );
86✔
461
}
123✔
462

463
int GEODIFF_applyChangesetEx(
60✔
464
  GEODIFF_ContextH contextHandle,
465
  const char *driverName,
466
  const char *driverExtraInfo,
467
  const char *base,
468
  const char *changeset )
469
{
470
  Context *context = static_cast<Context *>( contextHandle );
60✔
471
  if ( !context )
60✔
472
  {
473
    return GEODIFF_ERROR;
1✔
474
  }
475

476
  try
477
  {
478
    applyChangesetEx( context, driverName, driverExtraInfo, base, changeset );
59✔
479
  }
480
  catch ( const  GeoDiffException &exc )
5✔
481
  {
482
    return handleException( context, exc );
5✔
483
  }
5✔
484

485
  return GEODIFF_SUCCESS;
54✔
486
}
487

488

489
int GEODIFF_createRebasedChangeset(
22✔
490
  GEODIFF_ContextH contextHandle, const char *base,
491
  const char *modified,
492
  const char *changeset_their,
493
  const char *changeset,
494
  const char *conflictfile )
495
{
496
  Context *context = static_cast<Context *>( contextHandle );
22✔
497
  if ( !context )
22✔
498
  {
499
    return GEODIFF_ERROR;
1✔
500
  }
501

502
  if ( !conflictfile )
21✔
503
  {
504
    setAndLogError( context, "NULL arguments to GEODIFF_createRebasedChangeset" );
1✔
505
    return GEODIFF_ERROR;
1✔
506
  }
507
  fileremove( conflictfile );
20✔
508

509
  try
510
  {
511
    // first verify if we are able to do rebase on this database schema at all
512
    {
513
      std::map<std::string, std::string> conn;
20✔
514
      conn["base"] = std::string( modified );
80✔
515
      std::unique_ptr<Driver> driver( Driver::createDriver( context, "sqlite" ) );
20✔
516
      if ( !driver )
20✔
517
        throw GeoDiffException( "Unable to use driver: sqlite" );
×
518
      driver->open( conn );
20✔
519
    }
20✔
520

521
    TmpFile changeset_BASE_MODIFIED( std::string( changeset ) + "_BASE_MODIFIED" );
20✔
522
    int rc = GEODIFF_createChangeset( contextHandle, base, modified, changeset_BASE_MODIFIED.c_path() );
20✔
523
    if ( rc != GEODIFF_SUCCESS )
20✔
524
      return rc;
×
525

526
    return GEODIFF_createRebasedChangesetEx( contextHandle, "sqlite", "", base, changeset_BASE_MODIFIED.c_path(), changeset_their, changeset, conflictfile );
20✔
527
  }
20✔
528
  catch ( const  GeoDiffException &exc )
×
529
  {
530
    return handleException( context, exc );
×
531
  }
×
532
}
533

534
static void createRebasedChangesetEx(
64✔
535
  Context *context,
536
  const char *driverName,
537
  const char *driverExtraInfo,
538
  const char *base,
539
  const char *base2modified,
540
  const char *base2their,
541
  const char *rebased,
542
  const char *conflictfile )
543
{
544
  if ( !driverName || !base || !base2modified || !base2their || !rebased || !conflictfile )
64✔
545
  {
546
    throw GeoDiffException( "NULL arguments to GEODIFF_createRebasedChangesetEx" );
3✔
547
  }
548

549
  // Open the base DB to get its schema
550
  DatabaseSchema baseSchema;
63✔
551
  {
552
    DriverParametersMap conn;
63✔
553
    conn["base"] = std::string( base );
189✔
554
    if ( driverExtraInfo && *driverExtraInfo )
63✔
555
      conn["conninfo"] = std::string( driverExtraInfo );
68✔
556
    std::unique_ptr<Driver> driver( Driver::createDriver( context, std::string( driverName ) ) );
63✔
557
    if ( !driver )
63✔
NEW
558
      throw GeoDiffException( "Unable to use driver: " + std::string( driverName ) );
×
559
    driver->open( conn );
63✔
560
    for ( const std::string &tableName : driver->listTables() )
159✔
561
      baseSchema.tables.push_back( driver->tableSchema( tableName ) );
159✔
562
  }
63✔
563

564
  std::vector<ConflictFeature> conflicts;
63✔
565
  rebase( context, baseSchema, base2their, rebased, base2modified, conflicts );
318✔
566

567
  // output conflicts
568
  if ( conflicts.empty() )
60✔
569
  {
570
    context->logger().debug( "No conflicts present" );
156✔
571
  }
572
  else
573
  {
574
    nlohmann::json res = conflictsToJSON( conflicts );
8✔
575
    flushString( conflictfile, res.dump( 2 ) );
24✔
576
  }
8✔
577
}
66✔
578

579
int GEODIFF_createRebasedChangesetEx(
30✔
580
  GEODIFF_ContextH contextHandle,
581
  const char *driverName,
582
  const char *driverExtraInfo,
583
  const char *base,
584
  const char *base2modified,
585
  const char *base2their,
586
  const char *rebased,
587
  const char *conflictfile )
588
{
589
  Context *context = static_cast<Context *>( contextHandle );
30✔
590
  if ( !context )
30✔
591
  {
592
    return GEODIFF_ERROR;
1✔
593
  }
594

595
  try
596
  {
597
    createRebasedChangesetEx( context, driverName, driverExtraInfo, base, base2modified, base2their, rebased, conflictfile );
29✔
598
    return GEODIFF_SUCCESS;
26✔
599
  }
600
  catch ( const GeoDiffException &exc )
3✔
601
  {
602
    return handleException( context, exc );
3✔
603
  }
3✔
604
}
605

606

607
int GEODIFF_hasChanges(
177✔
608
  GEODIFF_ContextH contextHandle,
609
  const char *changeset )
610
{
611
  Context *context = static_cast<Context *>( contextHandle );
177✔
612
  if ( !context )
177✔
613
  {
614
    return -1;
1✔
615
  }
616

617
  if ( !changeset )
176✔
618
  {
619
    setAndLogError( context, "NULL arguments to GEODIFF_hasChanges" );
1✔
620
    return -1;
1✔
621
  }
622

623
  ChangesetReader reader;
175✔
624
  if ( !reader.open( changeset ) )
350✔
625
  {
626
    setAndLogError( context, "Could not open changeset: " + std::string( changeset ) );
2✔
627
    return -1;
2✔
628
  }
629

630
  return !reader.isEmpty();
173✔
631
}
175✔
632

633
int GEODIFF_changesCount(
87✔
634
  GEODIFF_ContextH contextHandle,
635
  const char *changeset )
636
{
637
  Context *context = static_cast<Context *>( contextHandle );
87✔
638
  if ( !context )
87✔
639
  {
640
    return -1;
1✔
641
  }
642

643
  if ( !changeset )
86✔
644
  {
645
    setAndLogError( context, "NULL arguments to GEODIFF_changesCount" );
1✔
646
    return -1;
1✔
647
  }
648

649
  ChangesetReader reader;
85✔
650
  if ( !reader.open( changeset ) )
170✔
651
  {
652
    setAndLogError( context, "Could not open changeset: " + std::string( changeset ) );
1✔
653
    return -1;
1✔
654
  }
655

656
  int changesCount = 0;
84✔
657
  ChangesetEntry entry;
84✔
658
  while ( reader.nextEntry( entry ) )
261✔
659
    if ( std::holds_alternative<ChangesetDataEntry>( entry ) )
177✔
660
      ++changesCount;
177✔
661

662
  return changesCount;
84✔
663
}
85✔
664

665
static int listChangesJSON( Context *context, const char *changeset, const char *jsonfile, bool onlySummary )
111✔
666
{
667
  if ( !changeset )
111✔
668
  {
669
    setAndLogError( context, "Not provided changeset file to listChangeset" );
2✔
670
    return GEODIFF_ERROR;
2✔
671
  }
672

673
  ChangesetReader reader;
109✔
674
  if ( !reader.open( changeset ) )
218✔
675
  {
676
    setAndLogError( context, "Could not open changeset: " + std::string( changeset ) );
2✔
677
    return GEODIFF_ERROR;
2✔
678
  }
679

680
  nlohmann::json res;
107✔
681
  try
682
  {
683
    if ( onlySummary )
107✔
684
      res = changesetToJSONSummary( reader );
52✔
685
    else
686
      res = changesetToJSON( reader );
55✔
687
  }
688
  catch ( const GeoDiffException &exc )
×
689
  {
690
    return handleException( context, exc );
×
691
  }
×
692

693
  if ( !jsonfile )
107✔
694
  {
695
    context->logger().info( res.dump( 2 ) );
44✔
696
  }
697
  else
698
  {
699
    flushString( jsonfile, res.dump( 2 ) );
189✔
700
  }
701

702
  return GEODIFF_SUCCESS;
107✔
703
}
109✔
704

705
int GEODIFF_listChanges(
58✔
706
  GEODIFF_ContextH contextHandle,
707
  const char *changeset,
708
  const char *jsonfile )
709
{
710
  Context *context = static_cast<Context *>( contextHandle );
58✔
711
  if ( !context )
58✔
712
  {
713
    return GEODIFF_ERROR;
1✔
714
  }
715
  return listChangesJSON( context, changeset, jsonfile, false );
57✔
716
}
717

718
int GEODIFF_listChangesSummary( GEODIFF_ContextH contextHandle, const char *changeset, const char *jsonfile )
55✔
719
{
720
  Context *context = static_cast<Context *>( contextHandle );
55✔
721
  if ( !context )
55✔
722
  {
723
    return GEODIFF_ERROR;
1✔
724
  }
725

726
  return listChangesJSON( context, changeset, jsonfile, true );
54✔
727
}
728

729
static void invertChangesetByPath( const char *changeset, const char *changeset_inv )
51✔
730
{
731
  if ( !changeset )
51✔
732
  {
733
    throw GeoDiffException( "NULL arguments to GEODIFF_invertChangeset" );
3✔
734
  }
735

736
  if ( !fileexists( changeset ) )
100✔
737
  {
738
    throw GeoDiffException( "Missing input files in GEODIFF_invertChangeset: " + std::string( changeset ) );
3✔
739
  }
740

741
  ChangesetReader reader;
49✔
742
  if ( !reader.open( changeset ) )
98✔
743
  {
744
    throw GeoDiffException( "Could not open changeset: " + std::string( changeset ) );
×
745
  }
746

747
  ChangesetWriter writer;
49✔
748
  writer.open( changeset_inv );
49✔
749

750
  invertChangeset( reader, writer );
49✔
751
}
49✔
752

753
int GEODIFF_invertChangeset( GEODIFF_ContextH contextHandle, const char *changeset, const char *changeset_inv )
18✔
754
{
755
  Context *context = static_cast<Context *>( contextHandle );
18✔
756
  if ( !context )
18✔
757
  {
758
    return GEODIFF_ERROR;
1✔
759
  }
760

761
  try
762
  {
763
    invertChangesetByPath( changeset, changeset_inv );
17✔
764
  }
765
  catch ( const GeoDiffException &exc )
2✔
766
  {
767
    return handleException( context, exc );
2✔
768
  }
2✔
769

770
  return GEODIFF_SUCCESS;
15✔
771
}
772

773

774
int GEODIFF_concatChanges(
26✔
775
  GEODIFF_ContextH contextHandle,
776
  int inputChangesetsCount,
777
  const char **inputChangesets,
778
  const char *outputChangeset )
779
{
780
  Context *context = static_cast<Context *>( contextHandle );
26✔
781
  if ( !context )
26✔
782
  {
783
    return GEODIFF_ERROR;
1✔
784
  }
785

786
  if ( inputChangesetsCount < 2 )
25✔
787
  {
788
    setAndLogError( context, "Need at least two input changesets in GEODIFF_concatChanges" );
1✔
789
    return GEODIFF_ERROR;
1✔
790
  }
791

792
  if ( !inputChangesets || !outputChangeset )
24✔
793
  {
794
    setAndLogError( context, "NULL arguments to GEODIFF_concatChanges" );
1✔
795
    return GEODIFF_ERROR;
1✔
796
  }
797

798
  std::vector<std::string> inputFiles;
23✔
799
  for ( int i = 0; i < inputChangesetsCount; ++i )
70✔
800
  {
801
    std::string filename = inputChangesets[i];
48✔
802
    if ( !fileexists( filename ) )
48✔
803
    {
804
      setAndLogError( context, "Input file in GEODIFF_concatChanges does not exist: " + filename );
1✔
805
      return GEODIFF_ERROR;
1✔
806
    }
807
    inputFiles.push_back( filename );
47✔
808
  }
48✔
809

810
  try
811
  {
812
    concatChangesets( context, inputFiles, outputChangeset );
22✔
813
  }
814
  catch ( const GeoDiffException &exc )
×
815
  {
816
    return handleException( context, exc );
×
817
  }
×
818

819
  return GEODIFF_SUCCESS;
22✔
820
}
23✔
821

822

823
int GEODIFF_rebase(
31✔
824
  GEODIFF_ContextH contextHandle,
825
  const char *base,
826
  const char *modified_their,
827
  const char *modified,
828
  const char *conflictfile )
829
{
830
  Context *context = static_cast<Context *>( contextHandle );
31✔
831
  if ( !context )
31✔
832
  {
833
    return GEODIFF_ERROR;
1✔
834
  }
835

836
  if ( !base || !modified_their || !modified || !conflictfile )
30✔
837
  {
838
    setAndLogError( context, "NULL arguments to GEODIFF_rebase" );
1✔
839
    return GEODIFF_ERROR;
1✔
840
  }
841

842
  if ( !fileexists( base ) )
58✔
843
  {
844
    setAndLogError( context, std::string( "Missing 'base' file in GEODIFF_rebase: " ) + base );
1✔
845
    return GEODIFF_ERROR;
1✔
846
  }
847

848
  if ( !fileexists( modified_their ) )
56✔
849
  {
850
    setAndLogError( context, std::string( "Missing 'modified_their' file in GEODIFF_rebase: " ) + modified_their );
1✔
851
    return GEODIFF_ERROR;
1✔
852
  }
853

854
  if ( !fileexists( modified ) )
54✔
855
  {
856
    setAndLogError( context, std::string( "Missing 'modified' file in GEODIFF_rebase: " ) + modified );
1✔
857
    return GEODIFF_ERROR;
1✔
858
  }
859

860
  std::string root = std::string( modified );
26✔
861

862
  TmpFile base2theirs( root + "_base2theirs.bin" );
26✔
863
  try
864
  {
865
    createChangesetEx( context, "sqlite", nullptr, base, modified_their, base2theirs.c_path() );
26✔
866
  }
867
  catch ( GeoDiffException &exc )
×
868
  {
869
    exc.addContext( "Unable to perform GEODIFF_createChangeset base2theirs" );
×
870
    return handleException( context, exc );
×
871
  }
×
872

873
  return GEODIFF_rebaseEx( contextHandle, "sqlite", "", base, modified, base2theirs.c_path(), conflictfile );
26✔
874
}
26✔
875

876

877
int GEODIFF_rebaseEx(
48✔
878
  GEODIFF_ContextH contextHandle,
879
  const char *driverName,
880
  const char *driverExtraInfo,
881
  const char *base,
882
  const char *modified,
883
  const char *base2their,
884
  const char *conflictfile )
885
{
886
  Context *context = static_cast<Context *>( contextHandle );
48✔
887
  if ( !context )
48✔
888
  {
889
    return GEODIFF_ERROR;
1✔
890
  }
891

892
  if ( !base || !modified || !base2their || !conflictfile )
47✔
893
  {
894
    setAndLogError( context, "NULL arguments to GEODIFF_rebase" );
1✔
895
    return GEODIFF_ERROR;
1✔
896
  }
897

898
  try
899
  {
900
    std::string root = tmpdir( ) + "geodiff_" + randomString( 6 );
46✔
901

902
    // situation 1: base2theirs is null, so we do not need rebase. modified is already fine
903
    if ( !GEODIFF_hasChanges( contextHandle, base2their ) )
46✔
904
    {
905
      return GEODIFF_SUCCESS;
7✔
906
    }
907

908
    TmpFile base2modified( root + "_base2modified.bin" );
39✔
909
    try
910
    {
911
      createChangesetEx( context, driverName, driverExtraInfo, base, modified, base2modified.c_path() );
39✔
912
    }
913
    catch ( GeoDiffException &exc )
2✔
914
    {
915
      exc.addContext( "Unable to perform GEODIFF_createChangeset base2modified" );
2✔
916
      return handleException( context, exc );
2✔
917
    }
2✔
918

919
    // situation 2: we do not have changes (modified == base), so result is modified_theirs
920
    if ( !GEODIFF_hasChanges( contextHandle, base2modified.c_path() ) )
37✔
921
    {
922
      try
923
      {
924
        applyChangesetEx( context, driverName, driverExtraInfo, modified, base2their );
2✔
925
      }
926
      catch ( GeoDiffException &exc )
1✔
927
      {
928
        exc.addContext( "Unable to perform GEODIFF_applyChangeset base2theirs" );
1✔
929
        return handleException( context, exc );
1✔
930
      }
1✔
931

932
      return GEODIFF_SUCCESS;
1✔
933
    }
934

935
    // situation 3: we have changes both in ours and theirs
936

937
    // 3A) Create all changesets
938
    TmpFile theirs2final( root + "_theirs2final.bin" );
35✔
939
    try
940
    {
941
      createRebasedChangesetEx( context, driverName, driverExtraInfo, base,
35✔
942
                                base2modified.c_path(), base2their,
943
                                theirs2final.c_path(), conflictfile );
944
    }
945
    catch ( GeoDiffException &exc )
1✔
946
    {
947
      exc.addContext( "Unable to perform GEODIFF_createChangeset theirs2final" );
1✔
948
      return handleException( context, exc );
1✔
949
    }
1✔
950

951
    TmpFile modified2base( root + "_modified2base.bin" );
34✔
952
    try
953
    {
954
      invertChangesetByPath( base2modified.c_path(), modified2base.c_path() );
34✔
955
    }
956
    catch ( GeoDiffException &exc )
×
957
    {
958
      exc.addContext( "Unable to perform GEODIFF_invertChangeset modified2base" );
×
959
      return handleException( context, exc );
×
960
    }
×
961

962
    // 3B) concat to single changeset
963
    TmpFile modified2final( root + "_modified2final.bin" );
34✔
964
    std::vector<std::string> concatInput;
34✔
965
    concatInput.push_back( modified2base.path() );
34✔
966
    concatInput.push_back( base2their );
34✔
967
    concatInput.push_back( theirs2final.path() );
34✔
968
    concatChangesets( context, concatInput, modified2final.path() );  // throws GeoDiffException exception on error
34✔
969

970
    // 3C) apply at once
971
    try
972
    {
973
      applyChangesetEx( context, driverName, driverExtraInfo, modified, modified2final.c_path() );
34✔
974
    }
975
    catch ( GeoDiffException &exc )
4✔
976
    {
977
      exc.addContext( "Unable to perform GEODIFF_applyChangeset modified2final" );
4✔
978
      return handleException( context, exc );
4✔
979
    }
4✔
980

981
    return GEODIFF_SUCCESS;
30✔
982
  }
46✔
983
  catch ( const GeoDiffException &exc )
×
984
  {
985
    return handleException( context, exc );
×
986
  }
×
987
}
988

989

990
int GEODIFF_makeCopy( GEODIFF_ContextH contextHandle,
25✔
991
                      const char *driverSrcName,
992
                      const char *driverSrcExtraInfo,
993
                      const char *src,
994
                      const char *driverDstName,
995
                      const char *driverDstExtraInfo,
996
                      const char *dst )
997
{
998
  Context *context = static_cast<Context *>( contextHandle );
25✔
999
  if ( !context )
25✔
1000
  {
1001
    return GEODIFF_ERROR;
1✔
1002
  }
1003

1004
  try
1005
  {
1006
    makeCopy( context,
24✔
1007
              driverSrcName,
1008
              driverSrcExtraInfo,
1009
              src,
1010
              driverDstName,
1011
              driverDstExtraInfo,
1012
              dst );
1013
  }
1014
  catch ( const GeoDiffException &exc )
3✔
1015
  {
1016
    return handleException( context, exc );
3✔
1017
  }
3✔
1018

1019
  return GEODIFF_SUCCESS;
21✔
1020
}
1021

1022
int GEODIFF_makeCopySqlite( GEODIFF_ContextH contextHandle, const char *src, const char *dst )
22✔
1023
{
1024
  Context *context = static_cast<Context *>( contextHandle );
22✔
1025
  if ( !context )
22✔
1026
  {
1027
    return GEODIFF_ERROR;
1✔
1028
  }
1029

1030
  if ( !src || !dst )
21✔
1031
  {
1032
    setAndLogError( context, "NULL arguments to GEODIFF_makeCopySqlite" );
3✔
1033
    return GEODIFF_ERROR;
3✔
1034
  }
1035

1036
  if ( !fileexists( src ) )
36✔
1037
  {
1038
    setAndLogError( context, "MakeCopySqlite: Source database does not exist: " + std::string( src ) );
2✔
1039
    return GEODIFF_ERROR;
2✔
1040
  }
1041

1042
  // If the destination file already exists, let's replace it. This is for convenience: if the file exists
1043
  // and it is SQLite database, the backup API would overwrite it, but if the file would not be a valid
1044
  // SQLite database, it would fail to open. With this check+remove we make sure that any existing file
1045
  // gets overwritten, regardless of its original content (making the API easier to use for the caller).
1046
  if ( fileexists( dst ) )
32✔
1047
  {
1048
    if ( fileremove( dst ) )
4✔
1049
    {
1050
      context->logger().warn( "MakeCopySqlite: Removed existing destination database: " + std::string( dst ) );
6✔
1051
    }
1052
    else
1053
    {
1054
      context->logger().error( "MakeCopySqlite: Failed to remove existing destination database: " + std::string( dst ) );
×
1055
    }
1056
  }
1057

1058
  Sqlite3Db dbFrom, dbTo;
16✔
1059
  try
1060
  {
1061
    dbFrom.open( src );
32✔
1062
  }
1063
  catch ( const GeoDiffException &e )
×
1064
  {
1065
    setAndLogError( context, "MakeCopySqlite: Unable to open source database: " + std::string( src ) + "\n" + e.what() );
×
1066
    return e.errorCode();
×
1067
  }
×
1068

1069
  try
1070
  {
1071
    dbTo.create( dst );
16✔
1072
  }
1073
  catch ( const GeoDiffException &e )
×
1074
  {
1075
    setAndLogError( context, "MakeCopySqlite: Unable to open destination database: " + std::string( dst ) + "\n" + e.what() );
×
1076
    return e.errorCode();
×
1077
  }
×
1078

1079
  // Set up the backup procedure to copy from the "main" database of
1080
  // connection pFrom to the main database of connection pTo.
1081
  // If something goes wrong, pBackup will be set to NULL and an error
1082
  // code and message left in connection pTo.
1083

1084
  // If the backup object is successfully created, call backup_step()
1085
  // to copy data from pFrom to pTo. Then call backup_finish()
1086
  // to release resources associated with the pBackup object.  If an
1087
  // error occurred, then an error code and message will be left in
1088
  // connection pTo. If no error occurred, then the error code belonging
1089
  // to pTo is set to SQLITE_OK.
1090

1091
  sqlite3_backup *pBackup = sqlite3_backup_init( dbTo.get(), "main", dbFrom.get(), "main" );
16✔
1092
  if ( pBackup )
16✔
1093
  {
1094
    sqlite3_backup_step( pBackup, -1 );
16✔
1095
    sqlite3_backup_finish( pBackup );
16✔
1096
  }
1097

1098
  std::string errorMsg;
16✔
1099
  if ( sqlite3_errcode( dbTo.get() ) )
16✔
1100
    errorMsg = sqlite3_errmsg( dbTo.get() );
×
1101

1102
  if ( !errorMsg.empty() )
16✔
1103
  {
1104
    setAndLogError( context, "MakeCopySqlite: backup failed: " + errorMsg );
×
1105
    return GEODIFF_ERROR;
×
1106
  }
1107

1108
  return GEODIFF_SUCCESS;
16✔
1109
}
16✔
1110

1111

1112
int GEODIFF_dumpData( GEODIFF_ContextH contextHandle, const char *driverName, const char *driverExtraInfo, const char *src, const char *changeset )
5✔
1113
{
1114
  Context *context = static_cast<Context *>( contextHandle );
5✔
1115
  if ( !context )
5✔
1116
  {
1117
    return GEODIFF_ERROR;
1✔
1118
  }
1119

1120
  if ( !driverName || !src || !changeset )
4✔
1121
  {
1122
    setAndLogError( context, "NULL arguments to GEODIFF_dumpData" );
1✔
1123
    return GEODIFF_ERROR;
1✔
1124
  }
1125

1126
  std::unique_ptr<Driver> driver( Driver::createDriver( context,  std::string( driverName ) ) );
3✔
1127
  if ( !driver )
3✔
1128
  {
1129
    setAndLogError( context, "Cannot create driver " + std::string( driverName ) );
1✔
1130
    return GEODIFF_ERROR;
1✔
1131
  }
1132

1133
  try
1134
  {
1135
    // open source
1136
    std::map<std::string, std::string> conn;
2✔
1137
    conn["base"] = std::string( src );
6✔
1138
    if ( driverExtraInfo )
2✔
1139
      conn["conninfo"] = std::string( driverExtraInfo );
8✔
1140
    driver->open( conn );
2✔
1141

1142
    // get source data
1143
    ChangesetWriter writer;
2✔
1144
    writer.open( changeset );
2✔
1145
    driver->dumpData( writer );
2✔
1146
  }
2✔
1147
  catch ( const GeoDiffException &exc )
×
1148
  {
1149
    return handleException( context, exc );
×
1150
  }
×
1151

1152
  return GEODIFF_SUCCESS;
2✔
1153
}
3✔
1154

1155

1156
int GEODIFF_schema( GEODIFF_ContextH contextHandle, const char *driverName, const char *driverExtraInfo, const char *src, const char *json )
10✔
1157
{
1158
  Context *context = static_cast<Context *>( contextHandle );
10✔
1159
  if ( !context )
10✔
1160
  {
1161
    return GEODIFF_ERROR;
1✔
1162
  }
1163

1164
  if ( !driverName || !src || !json )
9✔
1165
  {
1166
    setAndLogError( context, "NULL arguments to GEODIFF_schema" );
1✔
1167
    return GEODIFF_ERROR;
1✔
1168
  }
1169

1170
  std::unique_ptr<Driver> driver( Driver::createDriver( context,  std::string( driverName ) ) );
8✔
1171
  if ( !driver )
8✔
1172
  {
1173
    setAndLogError( context, "Cannot create driver " + std::string( driverName ) );
1✔
1174
    return GEODIFF_ERROR;
1✔
1175
  }
1176

1177
  try
1178
  {
1179
    // open source
1180
    std::map<std::string, std::string> conn;
7✔
1181
    conn["base"] = std::string( src );
21✔
1182
    if ( driverExtraInfo )
7✔
1183
      conn["conninfo"] = std::string( driverExtraInfo );
16✔
1184
    driver->open( conn );
7✔
1185

1186
    // prepare JSON
1187
    auto tablesData = nlohmann::json::array();
5✔
1188
    for ( const std::string &tableName : driver->listTables() )
9✔
1189
    {
1190
      const TableSchema tbl = driver->tableSchema( tableName );
4✔
1191

1192
      auto columnsJson = nlohmann::json::array();
4✔
1193
      for ( const TableColumnInfo &column : tbl.columns )
20✔
1194
      {
1195
        nlohmann::json columnData;
16✔
1196
        columnData[ "name" ] = column.name;
16✔
1197
        columnData[ "type" ] = TableColumnType::baseTypeToString( column.type.baseType );
16✔
1198
        columnData[ "type_db" ] = column.type.dbType;
16✔
1199
        if ( column.isPrimaryKey )
16✔
1200
          columnData[ "primary_key" ] = true;
4✔
1201
        if ( column.isNotNull )
16✔
1202
          columnData[ "not_null" ] = true;
4✔
1203
        if ( column.isAutoIncrement )
16✔
1204
          columnData[ "auto_increment" ] = true;
4✔
1205
        if ( column.isGeometry )
16✔
1206
        {
1207
          nlohmann::json geometryData;
4✔
1208
          geometryData[ "type" ] = column.geomType;
4✔
1209
          geometryData[ "srs_id" ] = std::to_string( column.geomSrsId );
4✔
1210
          if ( column.geomHasZ )
4✔
1211
            geometryData[ "has_z" ] = true;
×
1212
          if ( column.geomHasM )
4✔
1213
            geometryData[ "has_m" ] = true;
×
1214

1215
          columnData[ "geometry" ] = geometryData;
4✔
1216
        }
4✔
1217

1218
        columnsJson.push_back( columnData );
16✔
1219
      }
16✔
1220

1221
      nlohmann::json tableJson;
4✔
1222
      tableJson[ "table"] = tableName;
4✔
1223
      tableJson[ "columns" ] = columnsJson;
4✔
1224
      if ( tbl.crs.srsId != 0 )
4✔
1225
      {
1226
        nlohmann::json crsJson;
4✔
1227
        crsJson[ "srs_id" ] = tbl.crs.srsId;
4✔
1228
        crsJson[ "auth_name" ] = tbl.crs.authName;
4✔
1229
        crsJson[ "auth_code" ] = tbl.crs.authCode;
4✔
1230
        crsJson[ "wkt" ] = tbl.crs.wkt;
4✔
1231

1232
        tableJson[ "crs" ] = crsJson;
4✔
1233
      }
4✔
1234
      tablesData.push_back( tableJson );
4✔
1235
    }
9✔
1236

1237
    nlohmann::json res;
5✔
1238
    res[ "geodiff_schema" ] = tablesData;
5✔
1239

1240
    // write file content
1241
    flushString( json, res.dump( 2 ) );
15✔
1242
  }
7✔
1243
  catch ( const GeoDiffException &exc )
2✔
1244
  {
1245
    return handleException( context, exc );
2✔
1246
  }
2✔
1247

1248
  return GEODIFF_SUCCESS;
5✔
1249
}
8✔
1250

1251

1252
GEODIFF_ChangesetReaderH GEODIFF_readChangeset( GEODIFF_ContextH contextHandle, const char *changeset )
9✔
1253
{
1254
  Context *context = static_cast<Context *>( contextHandle );
9✔
1255
  if ( !context )
9✔
1256
  {
1257
    return nullptr;
1✔
1258
  }
1259

1260
  if ( !changeset )
8✔
1261
  {
1262
    setAndLogError( context, "NULL changeset argument to GEODIFF_readChangeset" );
1✔
1263
    return nullptr;
1✔
1264
  }
1265

1266
  ChangesetReader *reader = new ChangesetReader;
7✔
1267
  if ( !reader->open( changeset ) )
21✔
1268
  {
1269
    delete reader;
1✔
1270
    return nullptr;
1✔
1271
  }
1272
  return reader;
6✔
1273
}
1274

1275
GEODIFF_ChangesetEntryH GEODIFF_CR_nextEntry( GEODIFF_ContextH contextHandle, GEODIFF_ChangesetReaderH readerHandle, bool *ok )
17✔
1276
{
1277
  if ( !ok )
17✔
1278
  {
1279
    return nullptr;
1✔
1280
  }
1281

1282
  Context *context = static_cast<Context *>( contextHandle );
16✔
1283
  if ( !context )
16✔
1284
  {
1285
    *ok = false;
1✔
1286
    return nullptr;
1✔
1287
  }
1288

1289
  *ok = true;
15✔
1290
  ChangesetReader *reader = static_cast<ChangesetReader *>( readerHandle );
15✔
1291
  if ( !reader )
15✔
1292
  {
1293
    *ok = false;
1✔
1294
    return nullptr;
1✔
1295
  }
1296

1297
  std::unique_ptr<ChangesetEntry> entry( new ChangesetEntry );
14✔
1298
  try
1299
  {
1300
    if ( !reader->nextEntry( *entry ) )
14✔
1301
    {
1302
      // we have reached the end of file
1303
      return nullptr;
5✔
1304
    }
1305
  }
1306
  catch ( const GeoDiffException &exc )
1✔
1307
  {
1308
    setAndLogError( context, exc.what() );
1✔
1309
    *ok = false;
1✔
1310
    return nullptr;
1✔
1311
  }
1✔
1312
  return entry.release();
8✔
1313
}
14✔
1314

1315
void GEODIFF_CR_destroy( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetReaderH readerHandle )
6✔
1316
{
1317
  delete static_cast<ChangesetReader *>( readerHandle );
6✔
1318
}
6✔
1319

1320
static ChangesetDataEntry *getDataEntry( Context *context, GEODIFF_ChangesetEntryH entryHandle )
81✔
1321
{
1322
  ChangesetEntry *entry = static_cast<ChangesetEntry *>( entryHandle );
81✔
1323
  ChangesetDataEntry *dataEntry = std::get_if<ChangesetDataEntry>( entry );
81✔
1324
  if ( !dataEntry && context )
81✔
NEW
1325
    setAndLogError( context, "Entry is not a data entry" );
×
1326
  return dataEntry;
81✔
1327
}
1328

1329
int GEODIFF_CE_operation( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetEntryH entryHandle )
8✔
1330
{
1331
  const ChangesetEntry *entry = static_cast<ChangesetEntry *>( entryHandle );
8✔
1332
  return static_cast<int>( entry->operationType() );
8✔
1333
}
1334

1335
GEODIFF_ChangesetTableH GEODIFF_CE_table( GEODIFF_ContextH contextHandle, GEODIFF_ChangesetEntryH entryHandle )
7✔
1336
{
1337
  Context *context = static_cast<Context *>( contextHandle );
7✔
1338
  ChangesetDataEntry *dataEntry = getDataEntry( context, entryHandle );
7✔
1339
  if ( !dataEntry )
7✔
NEW
1340
    return nullptr;
×
1341
  return dataEntry->table.get();
7✔
1342
}
1343

1344
int GEODIFF_CE_countValues( GEODIFF_ContextH contextHandle, GEODIFF_ChangesetEntryH entryHandle )
7✔
1345
{
1346
  Context *context = static_cast<Context *>( contextHandle );
7✔
1347
  ChangesetDataEntry *dataEntry = getDataEntry( context, entryHandle );
7✔
1348
  if ( !dataEntry )
7✔
NEW
1349
    return GEODIFF_ERROR;
×
1350
  size_t ret = dataEntry->op == ChangesetDataEntry::OpDelete ? dataEntry->oldValues.size() : dataEntry->newValues.size();
7✔
1351
  return ( int ) ret;
7✔
1352
}
1353

1354
GEODIFF_ValueH GEODIFF_CE_oldValue( GEODIFF_ContextH contextHandle, GEODIFF_ChangesetEntryH entryHandle, int i )
32✔
1355
{
1356
  Context *context = static_cast<Context *>( contextHandle );
32✔
1357
  const ChangesetDataEntry *dataEntry = getDataEntry( context, entryHandle );
32✔
1358
  if ( !dataEntry )
32✔
NEW
1359
    return nullptr;
×
1360
  return new Value( dataEntry->oldValues[i] );
32✔
1361
}
1362

1363
GEODIFF_ValueH GEODIFF_CE_newValue( GEODIFF_ContextH contextHandle, GEODIFF_ChangesetEntryH entryHandle, int i )
35✔
1364
{
1365
  Context *context = static_cast<Context *>( contextHandle );
35✔
1366
  const ChangesetDataEntry *dataEntry = getDataEntry( context, entryHandle );
35✔
1367
  if ( !dataEntry )
35✔
NEW
1368
    return nullptr;
×
1369
  return new Value( dataEntry->newValues[i] );
35✔
1370
}
1371

1372
void GEODIFF_CE_destroy( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetEntryH entryHandle )
8✔
1373
{
1374
  delete static_cast<ChangesetEntry *>( entryHandle );
8✔
1375
}
8✔
1376

1377
int GEODIFF_V_type( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle )
67✔
1378
{
1379
  return static_cast<Value *>( valueHandle )->type();
67✔
1380
}
1381

1382
int64_t GEODIFF_V_getInt( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle )
7✔
1383
{
1384
  return static_cast<Value *>( valueHandle )->getInt();
7✔
1385
}
1386

1387
double GEODIFF_V_getDouble( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle )
4✔
1388
{
1389
  return static_cast<Value *>( valueHandle )->getDouble();
4✔
1390
}
1391

1392
void GEODIFF_V_destroy( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle )
67✔
1393
{
1394
  delete static_cast<Value *>( valueHandle );
67✔
1395
}
67✔
1396

1397
int GEODIFF_V_getDataSize( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle )
16✔
1398
{
1399
  size_t ret = static_cast<Value *>( valueHandle )->getString().size();
16✔
1400
  return ( int ) ret;
16✔
1401
}
1402

1403
void GEODIFF_V_getData( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ValueH valueHandle, char *data )
16✔
1404
{
1405
  const std::string &str = static_cast<Value *>( valueHandle )->getString();
16✔
1406
  memcpy( data, str.data(), str.size() );
16✔
1407
}
16✔
1408

1409
const char *GEODIFF_CT_name( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetTableH tableHandle )
7✔
1410
{
1411
  return static_cast<ChangesetTable *>( tableHandle )->name.data();
7✔
1412
}
1413

1414
int GEODIFF_CT_columnCount( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetTableH tableHandle )
7✔
1415
{
1416
  size_t ret = static_cast<ChangesetTable *>( tableHandle )->columnCount();
7✔
1417
  return ( int ) ret;
7✔
1418
}
1419

1420
bool GEODIFF_CT_columnIsPkey( GEODIFF_ContextH /*contextHandle*/, GEODIFF_ChangesetTableH tableHandle, int i )
39✔
1421
{
1422
  return static_cast<ChangesetTable *>( tableHandle )->primaryKeys.at( i );
39✔
1423
}
1424

1425
int GEODIFF_createWkbFromGpkgHeader( GEODIFF_ContextH contextHandle, const char *gpkgWkb, size_t gpkgLength, const char **wkb, size_t *wkbLength )
8✔
1426
{
1427
  const Context *context = static_cast<const Context *>( contextHandle );
8✔
1428
  if ( !context || !gpkgWkb || !wkb || !wkbLength )
8✔
1429
  {
1430
    return GEODIFF_ERROR;
6✔
1431
  }
1432

1433
  if ( gpkgLength == 0 )
2✔
1434
  {
1435
    return GEODIFF_ERROR;
×
1436
  }
1437

1438
  std::string gpkgWkbStr( gpkgWkb, gpkgLength );
2✔
1439
  int headerSize = parseGpkgbHeaderSize( gpkgWkbStr );
2✔
1440

1441
  size_t result_len = gpkgLength - headerSize;
2✔
1442
  *wkb = gpkgWkb + headerSize;
2✔
1443
  *wkbLength = result_len;
2✔
1444

1445
  return GEODIFF_SUCCESS;
2✔
1446
}
2✔
1447

1448
int GEODIFF_changesetHasSchemaChangeEntries( GEODIFF_ContextH contextHandle, const char *changeset, bool *schemaChangePresent )
2✔
1449
{
1450
  Context *context = static_cast<Context *>( contextHandle );
2✔
1451
  if ( !context || !changeset )
2✔
1452
  {
NEW
1453
    return GEODIFF_ERROR;
×
1454
  }
1455

1456
  try
1457
  {
1458
    ChangesetReader reader;
2✔
1459
    reader.open( changeset );
2✔
1460
    ChangesetEntry entry;
2✔
1461
    while ( reader.nextEntry( entry ) )
4✔
1462
    {
1463
      if ( !std::holds_alternative<ChangesetDataEntry>( entry ) )
3✔
1464
      {
1465
        *schemaChangePresent = true;
1✔
1466
        return GEODIFF_SUCCESS;
1✔
1467
      }
1468
    }
1469
    *schemaChangePresent = false;
1✔
1470
    return GEODIFF_SUCCESS;
1✔
1471
  }
2✔
NEW
1472
  catch ( const GeoDiffException &exc )
×
1473
  {
NEW
1474
    return handleException( context, exc );
×
NEW
1475
  }
×
1476
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc