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

MerginMaps / geodiff / 3710958510

pending completion
3710958510

push

github

GitHub
allow python logger to set to None (#192)

3047 of 4066 relevant lines covered (74.94%)

409.27 hits per line

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

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

6
#include "geodiff.h"
7
#include <iostream>
8
#include <stdio.h>
9
#include <string.h>
10
#include <string>
11

12
#include <fstream>
13
#include <vector>
14
#include <sstream>
15
#include "geodiffutils.hpp"
16
#include "geodiffcontext.hpp"
17
#include "driver.h"
18

19
static bool fileToStdout( const std::string &filepath )
×
20
{
21
#ifdef WIN32
22
  std::ifstream f;
23
  try
24
  {
25
    f.open( stringToWString( filepath ) );
26
  }
27
  catch ( GeoDiffException & )
28
  {
29
    return false;
30
  }
31
#else
32
  std::ifstream f( filepath );
×
33
#endif
34
  if ( !f.is_open() )
×
35
    return false;
×
36

37
  std::cout << f.rdbuf();
×
38
  return true;
×
39
}
×
40

41
static bool parseRequiredArgument( std::string &value, const std::vector<std::string> &args, size_t &i, const std::string &argName, const std::string &cmdName )
×
42
{
43
  if ( i < args.size() )
×
44
  {
45
    value = args[i++];
×
46
    return true;
×
47
  }
48
  else
49
  {
50
    std::cout << "Error: missing " << argName << " for '" << cmdName << "' command." << std::endl;
×
51
    return false;
×
52
  }
53
}
54

55
static bool checkNoExtraArguments( const std::vector<std::string> &args, size_t i, const std::string &cmdName )
×
56
{
57
  if ( i < args.size() )
×
58
  {
59
    std::cout << "Error: unexpected extra arguments for '" << cmdName << "' command." << std::endl;
×
60
    return false;
×
61
  }
62
  return true;
×
63
}
64

65
static bool isOption( const std::string &str )
×
66
{
67
  return str.size() > 0 && str[0] == '-';
×
68
}
69

70
static bool parseDriverOption( const std::vector<std::string> &args, size_t &i, const std::string &cmdName, std::string &driverName, std::string &driverOptions, std::string &tablesToSkip )
×
71
{
72
  for ( ; i < args.size(); ++i )
×
73
  {
74
    if ( !isOption( args[i] ) )
×
75
      break;  // no more options
×
76

77
    if ( args[i] == "--driver" )
×
78
    {
79
      if ( i + 2 >= args.size() )
×
80
      {
81
        std::cout << "Error: missing arguments for driver option" << std::endl;
×
82
        return false;
×
83
      }
84
      driverName = args[i + 1];
×
85
      driverOptions = args[i + 2];
×
86
      i += 2;
×
87
      continue;
×
88
    }
89
    else if ( args[i] == "--skip-tables" )
×
90
    {
91
      if ( i + 1 >= args.size() )
×
92
      {
93
        std::cout << "Error: missing arguments for skip-tables option" << std::endl;
×
94
        return 1;
×
95
      }
96
      tablesToSkip = args[i + 1];
×
97
      i += 1;
×
98
      continue;
×
99
    }
100
    else
101
    {
102
      std::cout << "Error: unknown option '" << args[i] << "' for '" << cmdName << "' command." << std::endl;
×
103
      return false;
×
104
    }
105
  }
106
  return true;
×
107
}
108

109
static std::vector<std::string> parseIgnoredTables( std::string &tablesToSkip )
×
110
{
111
  std::vector<std::string> tables;
×
112

113
  std::istringstream strm( tablesToSkip );
×
114
  std::string s;
×
115
  while ( getline( strm, s, ';' ) )
×
116
  {
117
    tables.push_back( s );
×
118
  }
119

120
  return tables;
×
121
}
×
122

123

124
static int handleCmdDiff( GEODIFF_ContextH context, const std::vector<std::string> &args )
×
125
{
126
  //   geodiff diff [OPTIONS...] DB_1 DB_2 [CH_OUTPUT]
127

128
  bool writeJson = false;
×
129
  bool writeSummary = false;
×
130
  bool printOutput = true;
×
131
  std::string db1, db2, chOutput;
×
132
  std::string driver1Name = "sqlite", driver2Name = "sqlite", driver1Options, driver2Options;
×
133
  std::string tablesToSkip;
×
134
  size_t i = 1;
×
135

136
  // parse options
137
  for ( ; i < args.size(); ++i )
×
138
  {
139
    if ( !isOption( args[i] ) )
×
140
      break;  // no more options
×
141

142
    if ( args[i] == "--json" )
×
143
    {
144
      writeJson = true;
×
145
      continue;
×
146
    }
147
    else if ( args[i] == "--summary" )
×
148
    {
149
      writeSummary = true;
×
150
      continue;
×
151
    }
152
    else if ( args[i] == "--driver" || args[i] == "--driver-1" || args[i] == "--driver-2" )
×
153
    {
154
      if ( i + 2 >= args.size() )
×
155
      {
156
        std::cout << "Error: missing arguments for driver option" << std::endl;
×
157
        return 1;
×
158
      }
159
      if ( args[i] == "--driver" || args[i] == "--driver-1" )
×
160
      {
161
        driver1Name = args[i + 1];
×
162
        driver1Options = args[i + 2];
×
163
      }
164
      if ( args[i] == "--driver" || args[i] == "--driver-2" )
×
165
      {
166
        driver2Name = args[i + 1];
×
167
        driver2Options = args[i + 2];
×
168
      }
169
      i += 2;
×
170
      continue;
×
171
    }
172
    else if ( args[i] == "--skip-tables" )
×
173
    {
174
      if ( i + 1 >= args.size() )
×
175
      {
176
        std::cout << "Error: missing arguments for skip-tables option" << std::endl;
×
177
        return 1;
×
178
      }
179
      tablesToSkip = args[i + 1];
×
180
      i += 1;
×
181
      continue;
×
182
    }
183
    else
184
    {
185
      std::cout << "Error: unknown option '" << args[i] << "' for 'diff' command." << std::endl;
×
186
      return 1;
×
187
    }
188
  }
189

190
  // check validity of options
191
  if ( writeJson && writeSummary )
×
192
  {
193
    std::cout << "Error: only one of the options can be passed: --json or --summary" << std::endl;
×
194
    return 1;
×
195
  }
196

197
  // set tables to skip
198
  Context *ctx = static_cast<Context *>( context );
×
199
  std::vector<std::string> tables = parseIgnoredTables( tablesToSkip );
×
200
  ctx->setTablesToSkip( tables );
×
201

202
  // parse required arguments
203
  if ( !parseRequiredArgument( db1, args, i, "DB_1", "diff" ) )
×
204
    return 1;
×
205
  if ( !parseRequiredArgument( db2, args, i, "DB_2", "diff" ) )
×
206
    return 1;
×
207

208
  if ( i < args.size() )
×
209
  {
210
    // optional output argument
211
    printOutput = false;
×
212
    chOutput = args[i++];
×
213

214
    if ( !checkNoExtraArguments( args, i, "diff" ) )
×
215
      return 1;
×
216
  }
217

218
  std::string changeset;
×
219
  TmpFile tmpChangeset;
×
220
  if ( printOutput || writeJson || writeSummary )
×
221
  {
222
    changeset = randomTmpFilename( );
×
223
    tmpChangeset.setPath( changeset );
×
224
  }
225
  else
226
    changeset = chOutput;
×
227

228
  if ( driver1Name == driver2Name && driver1Options == driver2Options )
×
229
  {
230
    int ret = GEODIFF_createChangesetEx( context,
×
231
                                         driver1Name.data(), driver1Options.data(),
232
                                         db1.data(), db2.data(), changeset.data() );
233
    if ( ret != GEODIFF_SUCCESS )
×
234
    {
235
      std::cout << "Error: diff failed!" << std::endl;
×
236
      return 1;
×
237
    }
238
  }
239
  else
240
  {
241
    int ret = GEODIFF_createChangesetDr( context,
×
242
                                         driver1Name.data(), driver1Options.data(), db1.data(),
243
                                         driver2Name.data(), driver2Options.data(), db2.data(),
244
                                         changeset.data() );
245
    if ( ret != GEODIFF_SUCCESS )
×
246
    {
247
      std::cout << "Error: diff failed!" << std::endl;
×
248
      return 1;
×
249
    }
250
  }
251

252
  if ( writeJson || writeSummary )
×
253
  {
254
    std::string json;
×
255
    TmpFile tmpJson;
×
256
    if ( printOutput )
×
257
    {
258
      json = randomTmpFilename( );
×
259
      tmpJson.setPath( json );
×
260
    }
261
    else
262
      json = chOutput;
×
263

264
    if ( writeJson )
×
265
    {
266
      if ( GEODIFF_listChanges( context, changeset.data(), json.data() ) != GEODIFF_SUCCESS )
×
267
      {
268
        std::cout << "Error: failed to convert changeset to JSON!" << std::endl;
×
269
        return 1;
×
270
      }
271
    }
272
    else  // writeSummary
273
    {
274
      if ( GEODIFF_listChangesSummary( context, changeset.data(), json.data() ) != GEODIFF_SUCCESS )
×
275
      {
276
        std::cout << "Error: failed to convert changeset to summary!" << std::endl;
×
277
        return 1;
×
278
      }
279
    }
280

281
    if ( printOutput )
×
282
    {
283
      if ( !fileToStdout( json ) )
×
284
      {
285
        std::cout << "Error: unable to read content of file " << json << std::endl;
×
286
        return 1;
×
287
      }
288
    }
289
  }
×
290
  else if ( printOutput )
×
291
  {
292
    if ( !fileToStdout( changeset ) )
×
293
    {
294
      std::cout << "Error: unable to read content of file " << changeset << std::endl;
×
295
      return 1;
×
296
    }
297
  }
298

299
  return 0;
×
300
}
×
301

302

303
static int handleCmdApply( GEODIFF_ContextH context, const std::vector<std::string> &args )
×
304
{
305
  // geodiff apply [OPTIONS...] DB CH_INPUT
306

307
  size_t i = 1;
×
308
  std::string db, changeset;
×
309
  std::string driverName = "sqlite", driverOptions;
×
310
  std::string tablesToSkip;
×
311

312
  // parse options
313
  if ( !parseDriverOption( args, i, "apply", driverName, driverOptions, tablesToSkip ) )
×
314
    return 1;
×
315

316
  // parse required arguments
317
  if ( !parseRequiredArgument( db, args, i, "DB", "apply" ) )
×
318
    return 1;
×
319
  if ( !parseRequiredArgument( changeset, args, i, "CH_INPUT", "apply" ) )
×
320
    return 1;
×
321
  if ( !checkNoExtraArguments( args, i, "apply" ) )
×
322
    return 1;
×
323

324
  Context *ctx = static_cast<Context *>( context );
×
325
  std::vector<std::string> tables = parseIgnoredTables( tablesToSkip );
×
326
  ctx->setTablesToSkip( tables );
×
327

328
  int ret = GEODIFF_applyChangesetEx( context, driverName.data(), driverOptions.data(), db.data(), changeset.data() );
×
329
  if ( ret != GEODIFF_SUCCESS )
×
330
  {
331
    std::cout << "Error: apply changeset failed!" << std::endl;
×
332
    return 1;
×
333
  }
334

335
  return 0;
×
336
}
×
337

338
static int handleCmdRebaseDiff( GEODIFF_ContextH context,  const std::vector<std::string> &args )
×
339
{
340
  // geodiff rebase-diff [OPTIONS...] DB_BASE CH_BASE_OUR CH_BASE_THEIR CH_REBASED CONFLICT
341

342
  size_t i = 1;
×
343
  std::string dbBase, chBaseOur, chBaseTheir, chRebased, conflict;
×
344
  std::string driverName = "sqlite", driverOptions;
×
345
  std::string tablesToSkip;
×
346

347
  // parse options
348
  if ( !parseDriverOption( args, i, "rebase-diff", driverName, driverOptions, tablesToSkip ) )
×
349
    return 1;
×
350

351
  if ( !parseRequiredArgument( dbBase, args, i, "DB_BASE", "rebase-diff" ) )
×
352
    return 1;
×
353
  if ( !parseRequiredArgument( chBaseOur, args, i, "CH_BASE_OUR", "rebase-diff" ) )
×
354
    return 1;
×
355
  if ( !parseRequiredArgument( chBaseTheir, args, i, "CH_BASE_THEIR", "rebase-diff" ) )
×
356
    return 1;
×
357
  if ( !parseRequiredArgument( chRebased, args, i, "CH_REBASED", "rebase-diff" ) )
×
358
    return 1;
×
359
  if ( !parseRequiredArgument( conflict, args, i, "CONFLICT", "rebase-diff" ) )
×
360
    return 1;
×
361
  if ( !checkNoExtraArguments( args, i, "rebase-diff" ) )
×
362
    return 1;
×
363

364
  Context *ctx = static_cast<Context *>( context );
×
365
  std::vector<std::string> tables = parseIgnoredTables( tablesToSkip );
×
366
  ctx->setTablesToSkip( tables );
×
367

368
  int ret = GEODIFF_createRebasedChangesetEx(
×
369
              context,
370
              driverName.data(), driverOptions.data(),
371
              dbBase.data(), chBaseOur.data(),
372
              chBaseTheir.data(), chRebased.data(), conflict.data() );
373
  if ( ret != GEODIFF_SUCCESS )
×
374
  {
375
    std::cout << "Error: rebase-diff failed!" << std::endl;
×
376
    return 1;
×
377
  }
378

379
  return 0;
×
380
}
×
381

382
static int handleCmdRebaseDb( GEODIFF_ContextH context, const std::vector<std::string> &args )
×
383
{
384
  // geodiff rebase-db [OPTIONS...] DB_BASE DB_OUR CH_BASE_THEIR CONFLICT
385

386
  size_t i = 1;
×
387
  std::string dbBase, dbOur, chBaseTheir, conflict;
×
388
  std::string driverName = "sqlite", driverOptions;
×
389
  std::string tablesToSkip;
×
390

391
  // parse options
392
  if ( !parseDriverOption( args, i, "rebase-db", driverName, driverOptions, tablesToSkip ) )
×
393
    return 1;
×
394

395
  if ( !parseRequiredArgument( dbBase, args, i, "DB_BASE", "rebase-db" ) )
×
396
    return 1;
×
397
  if ( !parseRequiredArgument( dbOur, args, i, "DB_OUR", "rebase-db" ) )
×
398
    return 1;
×
399
  if ( !parseRequiredArgument( chBaseTheir, args, i, "CH_BASE_THEIR", "rebase-db" ) )
×
400
    return 1;
×
401
  if ( !parseRequiredArgument( conflict, args, i, "CONFLICT", "rebase-db" ) )
×
402
    return 1;
×
403
  if ( !checkNoExtraArguments( args, i, "rebase-db" ) )
×
404
    return 1;
×
405

406
  // set tables to skip
407
  Context *ctx = static_cast<Context *>( context );
×
408
  std::vector<std::string> tables = parseIgnoredTables( tablesToSkip );
×
409
  ctx->setTablesToSkip( tables );
×
410

411
  int ret = GEODIFF_rebaseEx( context,
×
412
                              driverName.data(), driverOptions.data(), dbBase.data(), dbOur.data(),
413
                              chBaseTheir.data(), conflict.data() );
414
  if ( ret != GEODIFF_SUCCESS )
×
415
  {
416
    std::cout << "Error: rebase-db failed!" << std::endl;
×
417
    return 1;
×
418
  }
419

420
  return 0;
×
421
}
×
422

423
static int handleCmdInvert( GEODIFF_ContextH context, const std::vector<std::string> &args )
×
424
{
425
  // geodiff invert CH_INPUT CH_OUTPUT
426

427
  size_t i = 1;
×
428
  std::string chInput, chOutput;
×
429

430
  if ( !parseRequiredArgument( chInput, args, i, "CH_INPUT", "invert" ) )
×
431
    return 1;
×
432
  if ( !parseRequiredArgument( chOutput, args, i, "CH_OUTPUT", "invert" ) )
×
433
    return 1;
×
434
  if ( !checkNoExtraArguments( args, i, "invert" ) )
×
435
    return 1;
×
436

437
  int ret = GEODIFF_invertChangeset( context, chInput.data(), chOutput.data() );
×
438
  if ( ret != GEODIFF_SUCCESS )
×
439
  {
440
    std::cout << "Error: invert changeset failed!" << std::endl;
×
441
    return 1;
×
442
  }
443

444
  return 0;
×
445
}
×
446

447
static int handleCmdConcat( GEODIFF_ContextH context, const std::vector<std::string> &args )
×
448
{
449
  // geodiff concat CH_INPUT_1 CH_INPUT_2 [...] CH_OUTPUT
450

451
  if ( args.size() < 4 )
×
452
  {
453
    std::cout << "Error: 'concat' command needs at least two input changesets and one output changeset." << std::endl;
×
454
    return 1;
×
455
  }
456

457
  std::string chOutput = args[args.size() - 1];
×
458

459
  std::vector<const char *> changesets;
×
460
  for ( size_t i = 1; i < args.size() - 1; ++i )
×
461
  {
462
    changesets.push_back( args[i].data() );
×
463
  }
464

465
  int ret = GEODIFF_concatChanges( context, ( int ) changesets.size(), changesets.data(), chOutput.data() );
×
466
  if ( ret != GEODIFF_SUCCESS )
×
467
  {
468
    std::cout << "Error: concat changesets failed!" << std::endl;
×
469
    return 1;
×
470
  }
471

472
  return 0;
×
473
}
×
474

475
static int handleCmdAsJson( GEODIFF_ContextH context, const std::vector<std::string> &args )
×
476
{
477
  // geodiff as-json CH_INPUT [CH_OUTPUT]
478

479
  size_t i = 1;
×
480
  bool printOutput = true;
×
481
  std::string chInput, chOutput;
×
482

483
  // parse required arguments
484
  if ( !parseRequiredArgument( chInput, args, i, "CH_INPUT", "as-json" ) )
×
485
    return 1;
×
486

487
  if ( i < args.size() )
×
488
  {
489
    // optional output argument
490
    printOutput = false;
×
491
    chOutput = args[i++];
×
492

493
    if ( !checkNoExtraArguments( args, i, "as-json" ) )
×
494
      return 1;
×
495
  }
496

497
  std::string changeset;
×
498
  TmpFile tmpChangeset;
×
499
  if ( printOutput )
×
500
  {
501
    changeset = randomTmpFilename( );
×
502
    tmpChangeset.setPath( changeset );
×
503
  }
504
  else
505
    changeset = chOutput;
×
506

507
  int ret = GEODIFF_listChanges( context, chInput.data(), changeset.data() );
×
508
  if ( ret != GEODIFF_SUCCESS )
×
509
  {
510
    std::cout << "Error: export changeset to JSON failed!" << std::endl;
×
511
    return 1;
×
512
  }
513

514
  if ( printOutput )
×
515
  {
516
    if ( !fileToStdout( changeset ) )
×
517
    {
518
      std::cout << "Error: unable to read content of file " << changeset << std::endl;
×
519
      return 1;
×
520
    }
521
  }
522

523
  return 0;
×
524
}
×
525

526
static int handleCmdAsSummary( GEODIFF_ContextH context, const std::vector<std::string> &args )
×
527
{
528
  // geodiff as-summary CH_INPUT [SUMMARY]
529

530
  size_t i = 1;
×
531
  bool printOutput = true;
×
532
  std::string chInput, chOutput;
×
533

534
  // parse required arguments
535
  if ( !parseRequiredArgument( chInput, args, i, "CH_INPUT", "as-summary" ) )
×
536
    return 1;
×
537

538
  if ( i < args.size() )
×
539
  {
540
    // optional output argument
541
    printOutput = false;
×
542
    chOutput = args[i++];
×
543

544
    if ( !checkNoExtraArguments( args, i, "as-summary" ) )
×
545
      return 1;
×
546
  }
547

548
  std::string summary;
×
549
  TmpFile tmpSummary;
×
550
  if ( printOutput )
×
551
  {
552
    summary = randomTmpFilename( );
×
553
    tmpSummary.setPath( summary );
×
554
  }
555
  else
556
    summary = chOutput;
×
557

558
  int ret = GEODIFF_listChangesSummary( context, chInput.data(), summary.data() );
×
559
  if ( ret != GEODIFF_SUCCESS )
×
560
  {
561
    std::cout << "Error: export changeset to summary failed!" << std::endl;
×
562
    return 1;
×
563
  }
564

565
  if ( printOutput )
×
566
  {
567
    if ( !fileToStdout( summary ) )
×
568
    {
569
      std::cout << "Error: unable to read content of file " << summary << std::endl;
×
570
      return 1;
×
571
    }
572
  }
573

574
  return 0;
×
575
}
×
576

577
static int handleCmdCopy( GEODIFF_ContextH context, const std::vector<std::string> &args )
×
578
{
579
  // geodiff copy [OPTIONS...] DB_SOURCE DB_DESTINATION
580

581
  size_t i = 1;
×
582
  std::string chInput, chOutput;
×
583
  std::string driver1Name = "sqlite", driver1Options, driver2Name = "sqlite", driver2Options;
×
584
  std::string tablesToSkip;
×
585

586
  // parse options
587
  for ( ; i < args.size(); ++i )
×
588
  {
589
    if ( !isOption( args[i] ) )
×
590
      break;  // no more options
×
591

592
    if ( args[i] == "--driver" || args[i] == "--driver-1" || args[i] == "--driver-2" )
×
593
    {
594
      if ( i + 2 >= args.size() )
×
595
      {
596
        std::cout << "Error: missing arguments for driver option" << std::endl;
×
597
        return 1;
×
598
      }
599
      if ( args[i] == "--driver" || args[i] == "--driver-1" )
×
600
      {
601
        driver1Name = args[i + 1];
×
602
        driver1Options = args[i + 2];
×
603
      }
604
      if ( args[i] == "--driver" || args[i] == "--driver-2" )
×
605
      {
606
        driver2Name = args[i + 1];
×
607
        driver2Options = args[i + 2];
×
608
      }
609
      i += 2;
×
610
      continue;
×
611
    }
612
    else if ( args[i] == "--skip-tables" )
×
613
    {
614
      if ( i + 1 >= args.size() )
×
615
      {
616
        std::cout << "Error: missing arguments for skip-tables option" << std::endl;
×
617
        return 1;
×
618
      }
619
      tablesToSkip = args[i + 1];
×
620
      i += 1;
×
621
    }
622
    else
623
    {
624
      std::cout << "Error: unknown option '" << args[i] << "' for 'copy' command." << std::endl;
×
625
      return 1;
×
626
    }
627
  }
628

629
  if ( !parseRequiredArgument( chInput, args, i, "DB_SOURCE", "copy" ) )
×
630
    return 1;
×
631
  if ( !parseRequiredArgument( chOutput, args, i, "DB_DESTINATION", "copy" ) )
×
632
    return 1;
×
633
  if ( !checkNoExtraArguments( args, i, "copy" ) )
×
634
    return 1;
×
635

636
  Context *ctx = static_cast<Context *>( context );
×
637
  std::vector<std::string> tables = parseIgnoredTables( tablesToSkip );
×
638
  ctx->setTablesToSkip( tables );
×
639

640
  if ( driver1Name == "sqlite" && driver2Name == "sqlite" )
×
641
  {
642
    if ( !tablesToSkip.empty() )
×
643
    {
644
      std::cout << "Source and destination drivers are \"sqlite\". Option \"--skip-tables\" will be ignored." << std::endl;
×
645
      return 1;
×
646
    }
647

648
    int ret = GEODIFF_makeCopySqlite( context, chInput.data(), chOutput.data() );
×
649
    if ( ret != GEODIFF_SUCCESS )
×
650
    {
651
      std::cout << "Error: copy failed!" << std::endl;
×
652
      return 1;
×
653
    }
654
  }
655
  else
656
  {
657
    int ret = GEODIFF_makeCopy( context,
×
658
                                driver1Name.data(), driver1Options.data(), chInput.data(),
659
                                driver2Name.data(), driver2Options.data(), chOutput.data() );
660
    if ( ret != GEODIFF_SUCCESS )
×
661
    {
662
      std::cout << "Error: copy failed!" << std::endl;
×
663
      return 1;
×
664
    }
665
  }
666

667
  return 0;
×
668
}
×
669

670
static int handleCmdSchema( GEODIFF_ContextH context, const std::vector<std::string> &args )
×
671
{
672
  // geodiff schema [OPTIONS...] DB [SCHEMA_JSON]
673

674
  size_t i = 1;
×
675
  bool printOutput = true;
×
676
  std::string db, schemaJson;
×
677
  std::string driverName = "sqlite", driverOptions;
×
678
  std::string tablesToSkip;
×
679

680
  // parse options
681
  if ( !parseDriverOption( args, i, "schema", driverName, driverOptions, tablesToSkip ) )
×
682
    return 1;
×
683

684
  // parse required arguments
685
  if ( !parseRequiredArgument( db, args, i, "DB", "schema" ) )
×
686
    return 1;
×
687

688
  if ( i < args.size() )
×
689
  {
690
    // optional output argument
691
    printOutput = false;
×
692
    schemaJson = args[i++];
×
693

694
    if ( !checkNoExtraArguments( args, i, "schema" ) )
×
695
      return 1;
×
696
  }
697

698
  Context *ctx = static_cast<Context *>( context );
×
699
  std::vector<std::string> tables = parseIgnoredTables( tablesToSkip );
×
700
  ctx->setTablesToSkip( tables );
×
701

702
  std::string json;
×
703
  TmpFile tmpJson;
×
704
  if ( printOutput )
×
705
  {
706
    json = randomTmpFilename( );
×
707
    tmpJson.setPath( json );
×
708
  }
709
  else
710
    json = schemaJson;
×
711

712
  int ret = GEODIFF_schema( context, driverName.data(), driverOptions.data(), db.data(), json.data() );
×
713
  if ( ret != GEODIFF_SUCCESS )
×
714
  {
715
    std::cout << "Error: export changeset to summary failed!" << std::endl;
×
716
    return 1;
×
717
  }
718

719
  if ( printOutput )
×
720
  {
721
    if ( !fileToStdout( json ) )
×
722
    {
723
      std::cout << "Error: unable to read content of file " << json << std::endl;
×
724
      return 1;
×
725
    }
726
  }
727

728
  return 0;
×
729
}
×
730

731
static int handleCmdDump( GEODIFF_ContextH context, const std::vector<std::string> &args )
×
732
{
733
  // geodiff dump [OPTIONS...] DB CH_OUTPUT
734

735
  size_t i = 1;
×
736
  std::string db, chOutput;
×
737
  std::string driverName = "sqlite", driverOptions;
×
738
  std::string tablesToSkip;
×
739

740
  // parse options
741
  if ( !parseDriverOption( args, i, "dump", driverName, driverOptions, tablesToSkip ) )
×
742
    return 1;
×
743

744
  if ( !parseRequiredArgument( db, args, i, "DB", "dump" ) )
×
745
    return 1;
×
746
  if ( !parseRequiredArgument( chOutput, args, i, "CH_OUTPUT", "dump" ) )
×
747
    return 1;
×
748
  if ( !checkNoExtraArguments( args, i, "dump" ) )
×
749
    return 1;
×
750

751
  Context *ctx = static_cast<Context *>( context );
×
752
  std::vector<std::string> tables = parseIgnoredTables( tablesToSkip );
×
753
  ctx->setTablesToSkip( tables );
×
754

755
  int ret = GEODIFF_dumpData( context, driverName.data(), driverOptions.data(), db.data(), chOutput.data() );
×
756
  if ( ret != GEODIFF_SUCCESS )
×
757
  {
758
    std::cout << "Error: dump database failed!" << std::endl;
×
759
    return 1;
×
760
  }
761

762
  return 0;
×
763
}
×
764

765
static int handleCmdDrivers( const std::vector<std::string> &args )
×
766
{
767
  // geodiff drivers
768

769
  size_t i = 1;
×
770
  if ( !checkNoExtraArguments( args, i, "drivers" ) )
×
771
    return 1;
×
772

773
  for ( const std::string &driverName : Driver::drivers() )
×
774
  {
775
    std::cout << driverName << std::endl;
×
776
  }
×
777

778
  return 0;
×
779
}
780

781
static int handleCmdVersion( const std::vector<std::string> &args )
1✔
782
{
783
  ( void )args;
784

785
  std::cout << GEODIFF_version() << std::endl;
1✔
786
  return 0;
1✔
787
}
788

789
static GEODIFF_LoggerLevel getGeodiffLoggerLevel( )
1✔
790
{
791
  return ( GEODIFF_LoggerLevel ) getEnvVarInt( "GEODIFF_LOGGER_LEVEL", ( int ) LevelWarning );
1✔
792
}
793

794
static int handleCmdHelp( const std::vector<std::string> &args )
×
795
{
796
  ( void )args; // arguments unused
797

798
  std::cout << "GEODIFF " << GEODIFF_version() << ", a tool for handling diffs for geospatial data.\n\
×
799
\n\
800
Usage: geodiff <command> [args...]\n\
801
\n\
802
You can control verbosity using the environment variable GEODIFF_LOGGER_LEVEL:\n\
803
    0 = Nothing, 1 = Errors, 2 = Warnings, 3 = Info, 4 = Debug\n\
804
    (The default is 2 - showing only errors and warnings.)\n\
805
\n\
806
In the commands listed below, database files may be any GeoPackage files or other\n\
807
kinds of SQLite database files. This is using the default 'sqlite' driver. Even\n\
808
when 'sqlite' driver is specified in a command with --driver option, there are\n\
809
no extra driver options it needs (empty string \"\" can be passed).\n\
810
\n\
811
There may be other drivers available, for example 'postgres' driver. Its driver\n\
812
options expect the connection string as understood by its client library - either\n\
813
key/value pairs (e.g. \"host=localhost port=5432 dbname=mydb\") or connection URI\n\
814
(e.g. \"postgresql://localhost:5432/mydb\").\n\
815
\n\
816
Create and apply changesets (diffs):\n\
817
\n\
818
  geodiff diff [OPTIONS...] DB_1 DB_2 [CH_OUTPUT]\n\
819
\n\
820
    Creates a changeset (diff) between databases DB_BASE and DB_MODIFIED. If CH_OUTPUT\n\
821
    is specified, the result is written to that file, otherwise the output goes\n\
822
    to the standard output. By default, the changeset is written in the binary\n\
823
    format.\n\
824
\n\
825
    Options:\n\
826
      --json          Write changeset in JSON format instead of binary\n\
827
      --summary       Write only a summary for each table (in JSON)\n\
828
      --driver NAME DRIVER_OPTIONS\n\
829
                      Use driver NAME instead of the default 'sqlite' for both\n\
830
                      databases. Driver-specific options are provided in CONN_OPTIONS.\n\
831
      --driver-1 NAME DRIVER_OPTIONS\n\
832
                      Use driver NAME just for the first database. This allows creation\n\
833
                      of changesets across datasets in two different drivers.\n\
834
      --driver-2 NAME DRIVER_OPTIONS\n\
835
                      Use driver NAME just for the second database. This allows\n\
836
                      creation of changesets across datasets in two different drivers.\n\
837
      --skip-tables TABLES\n\
838
                      Ignore specified tables when creating a changeset. Tables are defined as\n\
839
                      a semicolon separated list of names.\n\
840
\n\
841
  geodiff apply [OPTIONS...] DB CH_INPUT\n\
842
\n\
843
    Applies a changeset (diff) from file CH_INPUT to the database file DB.\n\
844
    The changeset must be in the binary format (JSON format is not supported).\n\
845
\n\
846
    Options:\n\
847
      --driver NAME DRIVER_OPTIONS\n\
848
                      Use driver NAME instead of the default 'sqlite' for the\n\
849
                      database. Driver-specific options are provided in CONN_OPTIONS.\n\
850
      --skip-tables TABLES\n\
851
                      Ignore specified tables when applying a changeset. Tables are defined as\n\
852
                      a semicolon separated list of names.\n\
853
\n\
854
Rebasing:\n\
855
\n\
856
  geodiff rebase-diff [OPTIONS...] DB_BASE CH_BASE_OUR CH_BASE_THEIR CH_REBASED CONFLICT\n\
857
\n\
858
    Creates a rebased changeset. Using DB_BASE as the common base for \"our\" local\n\
859
    changes (CH_BASE_OUR) and \"their\" changes (CH_BASE_THEIR), the command will take\n\
860
    \"our\" changes and rebase them on top of \"their\" changes, and write results\n\
861
    to CH_REBASED file (containing just \"our\" changes, but modified to apply cleanly\n\
862
    on top of \"their\" changes). As a result, taking DB_BASE, applying CH_BASE_THEIR\n\
863
    and then applying CH_REBASED will result in a database containing both \"our\" and\n\
864
    \"their\" changes. If there were any conflicts during the rebase, they will be\n\
865
    written to CONFLICT file (in JSON format).\n\
866
\n\
867
    Options:\n\
868
      --driver NAME DRIVER_OPTIONS\n\
869
                      Use driver NAME instead of the default 'sqlite' for both\n\
870
                      databases. Driver-specific options are provided in CONN_OPTIONS.\n\
871
      --skip-tables TABLES\n\
872
                      Ignore specified tables when creating a rebased changeset. Tables are\n\
873
                      defined as a semicolon separated list of names.\n\
874
\n\
875
  geodiff rebase-db [OPTIONS...] DB_BASE DB_OUR CH_BASE_THEIR CONFLICT\n\
876
\n\
877
    Rebases database DB_OUR, using DB_BASE as the common base and CH_BASE_THEIR as the other\n\
878
    source of changes. CH_BASE_THEIR is a changeset containing changes between DB_BASE and\n\
879
    some other database. This will cause DB_OUR to be updated in-place to contain changes\n\
880
    (DB_OUR - DB_BASE) rebased on top of CH_BASE_THEIR. After successful rebase, DB_OUR will\n\
881
    contain both \"our\" and \"their\" changes. If there were any conflicts during\n\
882
    the rebase, they will be written to CONFLICT file (in JSON format).\n\
883
\n\
884
    Options:\n\
885
      --driver NAME DRIVER_OPTIONS\n\
886
                      Use driver NAME instead of the default 'sqlite' for all three\n\
887
                      databases. Driver-specific options are provided in CONN_OPTIONS.\n\
888
      --skip-tables TABLES\n\
889
                      Ignore specified tables when rebasing. Tables are defined as\n\
890
                      a semicolon separated list of names.\n\
891
\n\
892
Utilities:\n\
893
\n\
894
  geodiff invert CH_INPUT CH_OUTPUT\n\
895
\n\
896
    Inverts changeset in file CH_INPUT and writes inverted changeset to CH_OUTPUT.\n\
897
    Both input and output changesets are in the binary format.\n\
898
\n\
899
  geodiff concat CH_INPUT_1 CH_INPUT_2 [...] CH_OUTPUT\n\
900
\n\
901
    Concatenates two or more changeset files (CH_INPUT_1, CH_INPUT_2, ...) into a\n\
902
    single changeset. During concatenation, commands that act on the same rows get\n\
903
    merged together.\n\
904
\n\
905
  geodiff as-json CH_INPUT [CH_OUTPUT]\n\
906
\n\
907
    Converts the changeset in CH_INPUT file (in binary format) to JSON representation.\n\
908
    If CH_OUTPUT file is provided, it will be written to that file, otherwise it will\n\
909
    be written to the standard output.\n\
910
\n\
911
  geodiff as-summary CH_INPUT [SUMMARY]\n\
912
\n\
913
    Converts the changeset in CH_INPUT file (in binary format) to a summary JSON\n\
914
    which only contains overall counts of insert/update/delete commands for each table.\n\
915
\n\
916
  geodiff copy [OPTIONS...] DB_SOURCE DB_DESTINATION\n\
917
\n\
918
    Copies the source database DB_SOURCE to the destination database DB_DESTINATION.\n\
919
\n\
920
    Options:\n\
921
      --driver NAME DRIVER_OPTIONS\n\
922
                      Use driver NAME instead of the default 'sqlite' for both\n\
923
                      databases. Driver-specific options are provided in CONN_OPTIONS.\n\
924
      --driver-1 NAME DRIVER_OPTIONS\n\
925
                      Use driver NAME just for the first database. This allows creation\n\
926
                      of changesets across datasets in two different drivers.\n\
927
      --driver-2 NAME DRIVER_OPTIONS\n\
928
                      Use driver NAME just for the second database. This allows\n\
929
                      creation of changesets across datasets in two different drivers.\n\
930
      --skip-tables TABLES\n\
931
                      Ignore specified tables when copying the database. Tables are defined\n\
932
                      as a semicolon separated list of names.\n\
933
\n\
934
  geodiff schema [OPTIONS...] DB [SCHEMA_JSON]\n\
935
\n\
936
    Writes database schema of DB as understood by geodiff as JSON. If SCHEMA_JSON file\n\
937
    is provided, the output will be written to the file, otherwise the standard output\n\
938
    will be used.\n\
939
\n\
940
    Options:\n\
941
      --driver NAME DRIVER_OPTIONS\n\
942
                      Use driver NAME instead of the default 'sqlite' for the\n\
943
                      database. Driver-specific options are provided in CONN_OPTIONS.\n\
944
      --skip-tables TABLES\n\
945
                      Ignore specified tables when writing a schema. Tables are defined\n\
946
                      as a semicolon separated list of names.\n\
947
\n\
948
  geodiff dump [OPTIONS...] DB CH_OUTPUT\n\
949
\n\
950
    Dumps content of database DB to a changeset as a series of \"insert\" commands.\n\
951
\n\
952
    Options:\n\
953
      --driver NAME DRIVER_OPTIONS\n\
954
                      Use driver NAME instead of the default 'sqlite' for the\n\
955
                      database. Driver-specific options are provided in CONN_OPTIONS.\n\
956
      --skip-tables TABLES\n\
957
                      Ignore specified tables when dumping the database content. Tables\n\
958
                      are defined a semicolon separated list of names.\n\
959
\n\
960
  geodiff drivers\n\
961
\n\
962
    Prints the list of all drivers supported in this version. The \"sqlite\" driver\n\
963
    is always available.\n\
964
\n\
965
  geodiff version\n\
966
\n\
967
    Prints version of geodiff.\n\
968
\n\
969
  geodiff help\n\
970
\n\
971
    Prints this help information.\n\
972
\n\
973
Copyright (C) 2019-2021 Lutra Consulting\n\
974
";
975
  return 0;
×
976
}
977

978
class GeodiffContext
979
{
980
  public:
981
    GeodiffContext()
1✔
982
    {
1✔
983
      mContext = GEODIFF_createContext();
1✔
984
      GEODIFF_CX_setMaximumLoggerLevel( mContext,  getGeodiffLoggerLevel() );
1✔
985
    }
1✔
986
    ~GeodiffContext()
1✔
987
    {
988
      if ( mContext )
1✔
989
      {
990
        GEODIFF_CX_destroy( mContext );
1✔
991
        mContext = nullptr;
1✔
992
      }
993
    }
1✔
994
    GEODIFF_ContextH handle() {return mContext;}
×
995

996
  private:
997
    GEODIFF_ContextH mContext = nullptr;
998
};
999

1000

1001
int main( int argc, char *argv[] )
1✔
1002
{
1003
  GeodiffContext context;
1✔
1004

1005
  std::vector<std::string> args;
1✔
1006
  for ( int i = 1; i < argc; ++i )
2✔
1007
    args.push_back( argv[i] );
1✔
1008

1009
  if ( args.size() < 1 )
1✔
1010
  {
1011
    std::cout << "Error: missing command. See 'geodiff help' for a list of commands." << std::endl;
×
1012
    return 1;
×
1013
  }
1014

1015
  std::string command = args[0];
1✔
1016
  if ( command == "diff" )
1✔
1017
  {
1018
    return handleCmdDiff( context.handle(), args );
×
1019
  }
1020
  else if ( command == "apply" )
1✔
1021
  {
1022
    return handleCmdApply( context.handle(), args );
×
1023
  }
1024
  else if ( command == "rebase-diff" )
1✔
1025
  {
1026
    return handleCmdRebaseDiff( context.handle(),  args );
×
1027
  }
1028
  else if ( command == "rebase-db" )
1✔
1029
  {
1030
    return handleCmdRebaseDb( context.handle(),  args );
×
1031
  }
1032
  else if ( command == "invert" )
1✔
1033
  {
1034
    return handleCmdInvert( context.handle(),  args );
×
1035
  }
1036
  else if ( command == "concat" )
1✔
1037
  {
1038
    return handleCmdConcat( context.handle(), args );
×
1039
  }
1040
  else if ( command == "as-json" )
1✔
1041
  {
1042
    return handleCmdAsJson( context.handle(),  args );
×
1043
  }
1044
  else if ( command == "as-summary" )
1✔
1045
  {
1046
    return handleCmdAsSummary( context.handle(),  args );
×
1047
  }
1048
  else if ( command == "copy" )
1✔
1049
  {
1050
    return handleCmdCopy( context.handle(), args );
×
1051
  }
1052
  else if ( command == "schema" )
1✔
1053
  {
1054
    return handleCmdSchema( context.handle(),  args );
×
1055
  }
1056
  else if ( command == "dump" )
1✔
1057
  {
1058
    return handleCmdDump( context.handle(), args );
×
1059
  }
1060
  else if ( command == "drivers" )
1✔
1061
  {
1062
    return handleCmdDrivers( args );
×
1063
  }
1064
  else if ( command == "version" )
1✔
1065
  {
1066
    return handleCmdVersion( args );
1✔
1067
  }
1068
  else if ( command == "help" )
×
1069
  {
1070
    return handleCmdHelp( args );
×
1071
  }
1072
  else
1073
  {
1074
    std::cout << "Error: unknown command '" << command << "'. See 'geodiff help' for a list of commands." << std::endl;
×
1075
    return 1;
×
1076
  }
1077
}
1✔
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