<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2019 <NAME>
*/

#include "gtest/gtest.h"
#include "geodiff_testutils.hpp"
#include "geodiff.h"
#include "geodiffutils.hpp"

bool _test(
  const std::string &testname,
  const std::string &basename,
  const std::string &modifiedname,
  int expected_changes,
  bool ignore_timestamp_change = false
)
{
  std::cout << testname << std::endl;
  makedir( pathjoin( tmpdir(), testname ) );

  std::string base = pathjoin( testdir(), basename );
  std::string modified = pathjoin( testdir(), modifiedname );
  std::string changeset = pathjoin( tmpdir(), testname, "changeset.bin" );
  std::string changeset_inv = pathjoin( tmpdir(), testname, "changeset_inv.bin" );
  std::string patched = pathjoin( tmpdir(), testname, "patched.gpkg" );
  std::string patched2 = pathjoin( tmpdir(), testname, "patched2.gpkg" );
  std::string conflict = pathjoin( tmpdir(), testname, "conflict.json" );
  std::string json = pathjoin( tmpdir(), testname, testname + ".json" );
  std::string json_summary = pathjoin( tmpdir(), testname, testname + "_summary.json" );

  if ( GEODIFF_createChangeset( testContext(), base.c_str(), modified.c_str(), changeset.c_str() ) != GEODIFF_SUCCESS )
  {
    std::cout << "err GEODIFF_createChangeset" << std::endl;
    return false;
  }

  int nchanges = GEODIFF_changesCount( testContext(), changeset.c_str() );
  if ( nchanges != expected_changes )
  {
    std::cout << "err GEODIFF_listChanges " <<  nchanges << " vs " << expected_changes << std::endl;
    return false;
  }

  filecopy( patched, base );
  if ( GEODIFF_applyChangeset( testContext(), patched.c_str(), changeset.c_str() ) != GEODIFF_SUCCESS )
  {
    std::cout << "err GEODIFF_applyChangeset" << std::endl;
    return false;
  }

  // check that now it is same file with modified
  if ( !equals( patched, modified, ignore_timestamp_change ) )
  {
    std::cout << "err equals" << std::endl;
    return false;
  }

  // create inversed changeset
  if ( GEODIFF_invertChangeset( testContext(), changeset.c_str(), changeset_inv.c_str() ) != GEODIFF_SUCCESS )
  {
    std::cout << "err GEODIFF_invertChangeset" << std::endl;
    return false;
  }

  // apply inversed changeset
  if ( GEODIFF_applyChangeset( testContext(), patched.c_str(), changeset_inv.c_str() ) != GEODIFF_SUCCESS )
  {
    std::cout << "err GEODIFF_applyChangeset inversed" << std::endl;
    return false;
  }

  // check that now it is same file with base
  if ( !equals( patched, base, ignore_timestamp_change ) )
  {
    std::cout << "err equals" << std::endl;
    return false;
  }

  // check that direct rebase works
  filecopy( patched2, modified );
  if ( GEODIFF_rebase( testContext(), base.c_str(), base.c_str(), patched2.c_str(), conflict.c_str() ) != GEODIFF_SUCCESS )
  {
    std::cout << "err GEODIFF_rebase inversed" << std::endl;
    return false;
  }

  int nConflicts = countConflicts( conflict );
  if ( nConflicts != 0 )
  {
    std::cout << "conflicts found" << std::endl;
    return false;
  }

  if ( !equals( patched2, modified, ignore_timestamp_change ) )
  {
    std::cout << "err equals" << std::endl;
    return false;
  }

  printJSON( changeset, json, json_summary );

  return true;
}


TEST( SingleCommitSqlite3Test, test_sqlite_no_gis )
{
  std::cout << "sqlite 2 updated 1 added 1 deleted" << std::endl;
  bool ret = _test( "pure_sqlite",
                    "base.sqlite",
                    pathjoin( "pure_sqlite", "modified_base.sqlite" ),
                    4
                  );
  ASSERT_TRUE( ret );
}

TEST( SingleCommitSqlite3Test, geopackage )
{
  std::cout << "geopackage 1 updated geometry" << std::endl;
  bool ret = _test( "1_geopackage",
                    "base.gpkg",
                    pathjoin( "1_geopackage", "modified_1_geom.gpkg" ),
                    1
                  );
  ASSERT_TRUE( ret );
}

TEST( SingleCommitSqlite3Test, geopackage_complex )
{
  std::cout << "geopackage 2 new, 1 move, 1 changed attr, 1 delete" << std::endl;
  bool ret = _test( "complex",
                    "base.gpkg",
                    pathjoin( "complex", "complex1.gpkg" ),
                    6
                  );
  ASSERT_TRUE( ret );
}

TEST( SingleCommitSqlite3Test, retype_attribute )
{
  std::cout << "geopackage attribute count is same, have same name, but different type" << std::endl;
  bool ret = _test( "retype_attribute",
                    pathjoin( "modified_scheme", "added_attribute.gpkg" ),
                    pathjoin( "modified_scheme", "added_attribute_different_type.gpkg" ),
                    4
                  );
  ASSERT_FALSE( ret );
}

TEST( SingleCommitSqlite3Test, reprojected )
{
  std::cout << "geopackage change of crs" << std::endl;
  bool ret = _test( "reprojected",
                    pathjoin( "modified_scheme", "reprojected.gpkg" ),
                    pathjoin( "modified_scheme", "reprojected2.gpkg" ),
                    6
                  );
  ASSERT_FALSE( ret );
}

TEST( SingleCommitSqlite3Test, SingleCommitFkTest )
{
  std::cout << "database with foreign keys" << std::endl;
  bool ret = _test( "fk_1_update",
                    "base_fk.gpkg",
                    pathjoin( "fk_1_update", "modified_fk.gpkg" ),
                    3
                  );
  ASSERT_TRUE( ret );
}

TEST( SingleCommitSqlite3Test, GpkgTriggersTest )
{
  std::cout << "geopackage with many gpkg_ triggers" << std::endl;

  bool ret1 = _test( "gpkg_triggers",
                     pathjoin( "gpkg_triggers", "db-base.gpkg" ),
                     pathjoin( "gpkg_triggers", "db-modified.gpkg" ),
                     GEODIFF_changesCount( testContext(),
                         pathjoin( testdir(), "gpkg_triggers", "modified-changeset.diff" ).c_str() )
                   );

  bool ret2 = GEODIFF_createRebasedChangeset(
                testContext(),
                pathjoin( testdir(), "gpkg_triggers", "db-base.gpkg" ).c_str(),
                pathjoin( testdir(), "gpkg_triggers", "db-modified.gpkg" ).c_str(),
                pathjoin( testdir(), "gpkg_triggers", "modified-changeset.diff" ).c_str(),
                pathjoin( testdir(), "gpkg_triggers", "res.diff" ).c_str(),
                pathjoin( testdir(), "gpkg_triggers", "res.conflict" ).c_str()
              ) == GEODIFF_SUCCESS;

  ASSERT_TRUE( ret1 && ret2 );

  // remove created diff and conflict files
  remove( pathjoin( testdir(), "gpkg_triggers", "res.diff" ).c_str() );
  remove( pathjoin( testdir(), "gpkg_triggers", "res.conflict" ).c_str() );
}

TEST( SingleCommitSqlite3Test, NonAsciiCharactersTest )
{
  std::cout << "non ascii characters in path test" << std::endl;

  bool ret = _test(
               "non_ascii_\xc5\xa1", // add special sign also here, because changeset file is created from it
               pathjoin( "utf_test_\xc5\xa1\xc4\x8d\xc3\xa9", "test\xc3\xa1\xc3\xa1.gpkg" ), // testaa
               pathjoin( "utf_test_\xc5\xa1\xc4\x8d\xc3\xa9", "test\xc4\x8d\xc4\x8d.gpkg" ), // testcc
               2
             );
  ASSERT_TRUE( ret );
}

TEST( SingleCommitSqlite3Test, QuoteCharacterGpkgName )
{
  std::cout << "path with quote character" << std::endl;
  bool ret = _test(
               "quote's test",
               pathjoin( "dir_with_quote's's", "base.gpkg" ),
               pathjoin( "dir_with_quote's's", "recreated.gpkg" ),
               8
             );

  ASSERT_TRUE( ret );
}


int main( int argc, char **argv )
{
  testing::InitGoogleTest( &argc, argv );
  init_test();
  int ret =  RUN_ALL_TESTS();
  finalize_test();
  return ret;
}
<file_sep>
DROP SCHEMA IF EXISTS gd_tz_base CASCADE;

CREATE SCHEMA gd_tz_base;

CREATE TABLE gd_tz_base.simple ( "fid" SERIAL PRIMARY KEY, "geometry" GEOMETRY(POINT, 4326), "created" TIMESTAMP WITHOUT TIME ZONE);

INSERT INTO gd_tz_base.simple VALUES (1, ST_GeomFromText('Point (-1.08891928864569065 0.46101231190150482)', 4326), '2021-10-28 18:34:19.474');
INSERT INTO gd_tz_base.simple VALUES (2, ST_GeomFromText('Point (-0.36388508891928861 0.56224350205198359)', 4326), '2021-10-28 18:34:19.476');
INSERT INTO gd_tz_base.simple VALUES (3, ST_GeomFromText('Point (-0.73050615595075241 0.04240766073871405)', 4326));
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef POSTGRESDRIVER_H
#define POSTGRESDRIVER_H

#include "driver.h"

extern "C"
{
#include <libpq-fe.h>
}

// TODO: add docs!
class PostgresDriver : public Driver
{
  public:
    explicit PostgresDriver( const Context *context );
    ~PostgresDriver() override;

    void open( const DriverParametersMap &conn ) override;
    void create( const DriverParametersMap &conn, bool overwrite = false ) override;
    std::vector<std::string> listTables( bool useModified = false ) override;
    TableSchema tableSchema( const std::string &tableName, bool useModified = false ) override;
    void createChangeset( ChangesetWriter &writer ) override;
    void applyChangeset( ChangesetReader &reader ) override;
    void createTables( const std::vector<TableSchema> &tables ) override;
    void dumpData( ChangesetWriter &writer, bool useModified = false ) override;
    void checkCompatibleForRebase( bool useModified = false ) override;

  private:
    void logApplyConflict( const std::string &type, const ChangesetEntry &entry ) const;
    void openPrivate( const DriverParametersMap &conn );
    void close();
    std::string getSequenceObjectName( const TableSchema &tbl, int &autoIncrementPkeyIndex );
    void updateSequenceObject( const std::string &seqName, int64_t maxValue );

    PGconn *mConn = nullptr;
    std::string mBaseSchema;
    std::string mModifiedSchema;
};

#endif // POSTGRESDRIVER_H
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#include "geodiff.h"
#include "geodiffcontext.hpp"
#include "geodifflogger.hpp"
#include "geodiffutils.hpp"
#include <iostream>
#include <functional>
#include <algorithm>

Context::Context() = default;

Logger &Context::logger()
{
  return mLogger;
}

const Logger &Context::logger() const
{
  return mLogger;
}

void Context::setTablesToSkip( const std::vector<std::string> &tablesToSkip )
{
  mTablesToSkip = tablesToSkip;
}

bool Context::isTableSkipped( const std::string &tableName ) const
{
  if ( mTablesToSkip.empty() )
  {
    return false;
  }

  return std::any_of( mTablesToSkip.begin(), mTablesToSkip.end(), std::bind( std::equal_to< std::string >(), std::placeholders::_1, tableName ) );
}
<file_sep># GEODIFF (MIT License)
# Copyright (C) 2019 <NAME>

CMAKE_MINIMUM_REQUIRED(VERSION 3.10)
PROJECT(geodiffproject)
SET(CMAKE_CXX_VISIBILITY_PRESET hidden)
SET(CMAKE_VISIBILITY_INLINES_HIDDEN 1)

# if changed, change also run_cppcheck.sh
SET(CMAKE_CXX_STANDARD 11)

# set path to additional CMake modules
SET(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake ${CMAKE_MODULE_PATH})

SET(ENABLE_TESTS TRUE CACHE BOOL "Build tests?")
SET(ENABLE_COVERAGE FALSE CACHE BOOL "Enable GCOV code coverage?")
SET(BUILD_TOOLS TRUE CACHE BOOL "Build tool executables?")
SET(BUILD_STATIC FALSE CACHE BOOL "Build static libraries?")
SET(BUILD_SHARED TRUE CACHE BOOL "Build shared libraries?")
SET(WITH_POSTGRESQL FALSE CACHE BOOL "Whether to build with PostgreSQL driver")
SET(PEDANTIC TRUE CACHE BOOL "Determines if we should compile in pedantic mode.")

IF(WITH_INTERNAL_SQLITE3)
  MESSAGE(FATAL_ERROR "The WITH_INTERNAL_SQLITE3 option has been removed: geodiff now always uses external SQLite3 library")
ENDIF()

IF(SKBUILD)
  MESSAGE(STATUS "The geodiff is built using scikit-build for pygeodiff Python package")
  FIND_PACKAGE(PythonExtensions REQUIRED)
  SET(GEODIFF_NAME "pygeodiff-${PYGEODIFFVERSION}-python${GEODIFF_NAME_SUFFIX}")
  IF (CMAKE_GENERATOR_PLATFORM STREQUAL "Win32")
    SET(GEODIFF_NAME "${GEODIFF_NAME}-win32")
  ENDIF (CMAKE_GENERATOR_PLATFORM STREQUAL "Win32")
ELSE(SKBUILD)
  SET(GEODIFF_NAME geodiff)
ENDIF(SKBUILD)

#############################################################
# Setup code coverage
IF(ENABLE_COVERAGE)
  IF (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
    MESSAGE(FATAL_ERROR "gcov coverage must be run in debug build type")
  ENDIF(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")

  IF(MSVC)
    MESSAGE(FATAL_ERROR "gcov coverage is not implemented for Windows")
  ENDIF(MSVC)

  SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage")
ENDIF(ENABLE_COVERAGE)

IF(NOT "${CMAKE_VERSION}" VERSION_LESS "3.12")
  CMAKE_POLICY(SET CMP0074 NEW)
ENDIF(NOT "${CMAKE_VERSION}" VERSION_LESS "3.12")
FIND_PACKAGE(SQLite3 REQUIRED)
MESSAGE(STATUS "SQLite3 version: ${SQLite3_VERSION}")
MESSAGE(STATUS "SQLite3 include dirs: ${SQLite3_INCLUDE_DIRS}")
MESSAGE(STATUS "SQLite3 library: ${SQLite3_LIBRARIES}")

IF (WITH_POSTGRESQL)
  FIND_PACKAGE(Postgres REQUIRED)
  IF (POSTGRES_FOUND)
    SET(HAVE_POSTGRES TRUE)   # used in geodiff_config.hpp
  ENDIF()
ENDIF()

# Get libgpkg dependency
IF(EXISTS "${CMAKE_BINARY_DIR}/libgpkg.tar.gz")
  MESSAGE("libgpkg already downloaded")
ELSE()
  FILE(DOWNLOAD "https://github.com/benstadin/libgpkg/archive/0822c5cba7e1ac2c2806e445e5f5dd2f0d0a18b4.tar.gz" ${CMAKE_BINARY_DIR}/libgpkg.tar.gz )
  FILE(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/external")
  EXECUTE_PROCESS(COMMAND ${CMAKE_COMMAND} -E tar xfz ${CMAKE_BINARY_DIR}/libgpkg.tar.gz
    WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/external
    RESULT_VARIABLE rv)
ENDIF()

SET(libgpkg_dir ${CMAKE_BINARY_DIR}/external/libgpkg-0822c5cba7e1ac2c2806e445e5f5dd2f0d0a18b4)
SET(libgpkg_src
  ${libgpkg_dir}/gpkg/binstream.c
  ${libgpkg_dir}/gpkg/blobio.c
  ${libgpkg_dir}/gpkg/error.c
  ${libgpkg_dir}/gpkg/fp.c
  ${libgpkg_dir}/gpkg/geomio.c
  ${libgpkg_dir}/gpkg/gpkg.c
  ${libgpkg_dir}/gpkg/gpkg_db.c
  ${libgpkg_dir}/gpkg/gpkg_geom.c
  ${libgpkg_dir}/gpkg/i18n.c
  ${libgpkg_dir}/gpkg/sql.c
  ${libgpkg_dir}/gpkg/spatialdb.c
  ${libgpkg_dir}/gpkg/spl_db.c
  ${libgpkg_dir}/gpkg/spl_geom.c
  ${libgpkg_dir}/gpkg/strbuf.c
  ${libgpkg_dir}/gpkg/wkb.c
  ${libgpkg_dir}/gpkg/wkt.c
  ${libgpkg_dir}/gpkg/atomic_ops.h
)

IF (NOT WIN32)
  SET_SOURCE_FILES_PROPERTIES(${libgpkg_src} PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations")
ENDIF (NOT WIN32)

INCLUDE(CheckIncludeFile)
INCLUDE( ${libgpkg_dir}/gpkg/cmake/UseTLS.cmake )
CHECK_TLS()

INCLUDE( ${libgpkg_dir}/gpkg/cmake/UseLocale.cmake )
CHECK_LOCALE()

# check the version in the libgpkg main CMakeLists.txt
SET(gpkg_VERSION_MAJOR 0)
SET(gpkg_VERSION_MINOR 10)
SET(gpkg_VERSION_PATCH 0)
ADD_DEFINITIONS( -DGPKG_HAVE_CONFIG_H )
CONFIGURE_FILE( "${libgpkg_dir}/gpkg/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h" )
INCLUDE_DIRECTORIES(
  ${CMAKE_CURRENT_SOURCE_DIR}/src
  ${CMAKE_CURRENT_SOURCE_DIR}/src/3rdparty
  ${CMAKE_CURRENT_SOURCE_DIR}/src/drivers
  ${CMAKE_CURRENT_BINARY_DIR}
  ${SQLite3_INCLUDE_DIRS}
)

# create geodiff_config.h
CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/cmake_templates/geodiff_config.hpp.in ${CMAKE_BINARY_DIR}/geodiff_config.hpp)

# build
SET(geodiff_src
  ${geodiff_src}
  src/geodiff.cpp
  src/geodiff.h
  src/geodiffutils.cpp
  src/geodiffutils.hpp
  src/geodiffrebase.cpp
  src/geodiffrebase.hpp
  src/geodifflogger.cpp
  src/geodifflogger.hpp
  src/geodiffcontext.cpp
  src/geodiffcontext.hpp

  src/changeset.h
  src/changesetconcat.cpp
  src/changesetreader.cpp
  src/changesetreader.h
  src/changesetutils.cpp
  src/changesetutils.h
  src/changesetwriter.cpp
  src/changesetwriter.h
  src/driver.cpp
  src/driver.h
  src/tableschema.cpp
  src/tableschema.h

  src/drivers/sqlitedriver.cpp
  src/drivers/sqlitedriver.h
  src/drivers/sqliteutils.cpp
  src/drivers/sqliteutils.h
)

SET(external_3rd_party_src
  src/3rdparty/base64utils.cpp
  src/3rdparty/base64utils.h
  src/3rdparty/portableendian.h
  src/3rdparty/changesetgetvarint.h
  src/3rdparty/changesetputvarint.h
  src/3rdparty/json.hpp
)

IF (POSTGRES_FOUND)
  SET(geodiff_src
    ${geodiff_src}
    src/drivers/postgresdriver.cpp
    src/drivers/postgresdriver.h
    src/drivers/postgresutils.cpp
    src/drivers/postgresutils.h
  )
ENDIF()

IF (ENABLE_TESTS OR BUILD_TOOLS)
  # tests need statically built library in order to use symbols that are not exported
  SET ( BUILD_STATIC TRUE )
ENDIF ()

IF ( BUILD_STATIC )
  ADD_LIBRARY(${GEODIFF_NAME}_a STATIC ${geodiff_src} ${libgpkg_src} ${external_3rd_party_src} )
  SET_TARGET_PROPERTIES(${GEODIFF_NAME}_a PROPERTIES OUTPUT_NAME ${GEODIFF_NAME})
  TARGET_COMPILE_DEFINITIONS(${GEODIFF_NAME}_a PUBLIC -DGEODIFF_STATIC)
  TARGET_INCLUDE_DIRECTORIES(${GEODIFF_NAME}_a PRIVATE ${libgpkg_dir}/gpkg)
  IF (POSTGRES_FOUND)
    TARGET_INCLUDE_DIRECTORIES(${GEODIFF_NAME}_a PRIVATE ${POSTGRES_INCLUDE_DIR})
  ENDIF ()

  # win32 libs
  IF ( WIN32 )
    TARGET_LINK_LIBRARIES( ${GEODIFF_NAME}_a PUBLIC shlwapi )
  ENDIF ()
ENDIF ()

IF ( BUILD_SHARED )
    ADD_LIBRARY(${GEODIFF_NAME} SHARED ${geodiff_src} ${libgpkg_src} ${external_3rd_party_src} )
    IF (SKBUILD AND WIN32)
        # looks like delvewheel does not support dll extension at the moment
        SET_TARGET_PROPERTIES(${GEODIFF_NAME} PROPERTIES SUFFIX .pyd)
    ENDIF (SKBUILD AND WIN32)

    IF (NOT WIN32 AND NOT ANDROID AND NOT IOS)
        TARGET_LINK_LIBRARIES(${GEODIFF_NAME} PUBLIC dl pthread)
    ENDIF (NOT WIN32 AND NOT ANDROID AND NOT IOS)

    TARGET_LINK_LIBRARIES(${GEODIFF_NAME} PUBLIC ${SQLite3_LIBRARIES})
    TARGET_INCLUDE_DIRECTORIES(${GEODIFF_NAME} PRIVATE ${libgpkg_dir}/gpkg)

    IF (POSTGRES_FOUND)
      TARGET_INCLUDE_DIRECTORIES(${GEODIFF_NAME} PRIVATE ${POSTGRES_INCLUDE_DIR})
      TARGET_LINK_LIBRARIES(${GEODIFF_NAME} PUBLIC ${POSTGRES_LIBRARY})
    ENDIF ()

    # win32 libs
    IF ( WIN32 )
      TARGET_LINK_LIBRARIES( ${GEODIFF_NAME} PUBLIC shlwapi )
    ENDIF ()
ENDIF ()

# command line tool
IF (BUILD_TOOLS)
    SET(geodiff_cli_src src/geodiff-cli.cpp)

    ADD_EXECUTABLE(geodiff-cli ${geodiff_cli_src})
    TARGET_LINK_LIBRARIES(geodiff-cli PUBLIC ${GEODIFF_NAME}_a )
    SET_TARGET_PROPERTIES(geodiff-cli PROPERTIES OUTPUT_NAME geodiff)
    TARGET_LINK_LIBRARIES(geodiff-cli PUBLIC ${SQLite3_LIBRARIES} )
    IF (POSTGRES_FOUND)
        TARGET_LINK_LIBRARIES(geodiff-cli PUBLIC ${POSTGRES_LIBRARY})
    ENDIF ()
ENDIF (BUILD_TOOLS)

ADD_DEFINITIONS( -DSQLITE_CORE )

# tests
IF (ENABLE_TESTS)
    INCLUDE (CTest)
    ADD_DEFINITIONS(-DENABLE_TESTS)
    ENABLE_TESTING()
    ADD_SUBDIRECTORY(tests)
ENDIF(ENABLE_TESTS)

#############################################################
# Setup pedantic warnings
IF (PEDANTIC)
  IF (MSVC)
    # warning level 4 and all warnings as errors.
    set_source_files_properties(${geodiff_src} ${geodiff_cli_src} PROPERTIES COMPILE_OPTIONS "/W4;/WX")
  ELSE(MSVC)
    # lots of warnings and all warnings as errors
    set_source_files_properties(${geodiff_src} ${geodiff_cli_src} PROPERTIES COMPILE_OPTIONS "-Wall;-Wextra;-pedantic;-Werror")
  ENDIF(MSVC)
ENDIF(PEDANTIC)

# install
IF(SKBUILD)
    IF(WIN32)
        INSTALL(TARGETS ${GEODIFF_NAME} RUNTIME DESTINATION pygeodiff)
    ELSE(WIN32)
        INSTALL(TARGETS ${GEODIFF_NAME} LIBRARY DESTINATION pygeodiff)
    ENDIF(WIN32)
ELSE(SKBUILD)
    IF (BUILD_SHARED)
        IF(WIN32)
            INSTALL(TARGETS ${GEODIFF_NAME}
              RUNTIME DESTINATION bin
              ARCHIVE DESTINATION lib
            )
        ELSE(WIN32)
            INSTALL(TARGETS ${GEODIFF_NAME}
              LIBRARY DESTINATION lib
              ARCHIVE DESTINATION lib
            )
        ENDIF(WIN32)
    ENDIF()

    IF (BUILD_STATIC)
        IF(WIN32)
            INSTALL(TARGETS ${GEODIFF_NAME}_a
              RUNTIME DESTINATION bin
              ARCHIVE DESTINATION lib
            )
        ELSE(WIN32)
            INSTALL(TARGETS ${GEODIFF_NAME}_a
              LIBRARY DESTINATION lib
              ARCHIVE DESTINATION lib
            )
        ENDIF(WIN32)
    ENDIF()

    INSTALL(FILES src/geodiff.h DESTINATION include)
ENDIF(SKBUILD)
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef CHANGESETUTILS_H
#define CHANGESETUTILS_H

#include "geodiff.h"
#include <string>
#include <vector>

#include "json.hpp"

class ConflictFeature;
class ChangesetReader;
class ChangesetWriter;
struct ChangesetEntry;
struct ChangesetTable;
struct TableSchema;
struct Value;
class Context;

ChangesetTable schemaToChangesetTable( const std::string &tableName, const TableSchema &tbl );

void invertChangeset( ChangesetReader &reader, ChangesetWriter &writer );

void concatChangesets( const Context *context, const std::vector<std::string> &filenames, const std::string &outputChangeset );

nlohmann::json changesetEntryToJSON( const ChangesetEntry &entry );

nlohmann::json changesetToJSON( ChangesetReader &reader );

nlohmann::json changesetToJSONSummary( ChangesetReader &reader );

nlohmann::json conflictsToJSON( const std::vector<ConflictFeature> &conflicts );

nlohmann::json valueToJSON( const Value &value );

std::string hex2bin( const std::string &str );
std::string bin2hex( const std::string &str );

#endif // CHANGESETUTILS_H
<file_sep>
DROP SCHEMA IF EXISTS gd_deleted_a CASCADE;

CREATE SCHEMA gd_deleted_a;

CREATE TABLE gd_deleted_a.simple ( "fid" SERIAL PRIMARY KEY, "geometry" GEOMETRY(POINT, 4326), "name" TEXT, "rating" INTEGER);

INSERT INTO gd_deleted_a.simple VALUES (1, ST_GeomFromText('Point (-1.08891928864569065 0.46101231190150482)', 4326), 'feature1', 1);
INSERT INTO gd_deleted_a.simple VALUES (3, ST_GeomFromText('Point (-0.73050615595075241 0.04240766073871405)', 4326), 'feature3', 3);
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef SQLITEDRIVER_H
#define SQLITEDRIVER_H

#include "driver.h"
#include "sqliteutils.h"

/**
 * Support for diffs between Sqlite-based files (including GeoPackage)
 *
 * Connection configuration:
 *
 * - for a single database use (not possible to call createChangeset())
 *   - "base" = path to the database
 *
 * - for use with two databases (possible to call createChangeset())
 *   - "base" = path to the 'base' database
 *   - "modified" = path to the 'modified' database
 */
class SqliteDriver : public Driver
{
  public:
    explicit SqliteDriver( const Context *context );

    void open( const DriverParametersMap &conn ) override;
    void create( const DriverParametersMap &conn, bool overwrite = false ) override;
    std::vector<std::string> listTables( bool useModified = false ) override;
    TableSchema tableSchema( const std::string &tableName, bool useModified = false ) override;
    void createChangeset( ChangesetWriter &writer ) override;
    void applyChangeset( ChangesetReader &reader ) override;
    void createTables( const std::vector<TableSchema> &tables ) override;
    void dumpData( ChangesetWriter &writer, bool useModified = false ) override;
    void checkCompatibleForRebase( bool useModified = false ) override;

  private:
    void logApplyConflict( const std::string &type, const ChangesetEntry &entry ) const;
    std::string databaseName( bool useModified = false );

    std::shared_ptr<Sqlite3Db> mDb;
    bool mHasModified = false;  // whether there is also a second file attached
};


#endif // SQLITEDRIVER_H
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2021 <NAME>
*/

#include "gtest/gtest.h"
#include "geodiff_testutils.hpp"
#include "geodiff.h"

#include "geodiffutils.hpp"

TEST( UtilsTest, test_unicode )
{
  std::string diffSource = pathjoin( testdir(), "utf_test_ščé", "changes.diff" );
  std::string json = pathjoin( tmpdir(), "čúčo.json" );

  // test whether unicode characters are working
  EXPECT_EQ( GEODIFF_listChanges( testContext(), diffSource.c_str(), json.c_str() ), GEODIFF_SUCCESS );

  // make sure our test util functions can deal with unicode
  EXPECT_TRUE( fileexists( json ) );
  EXPECT_FALSE( isFileEmpty( json ) );
  EXPECT_TRUE( fileContains( json, "geodiff" ) );

  printFileToStdout( "CUCO.JSON", json );
}


int main( int argc, char **argv )
{
  testing::InitGoogleTest( &argc, argv );
  init_test();
  int ret =  RUN_ALL_TESTS();
  finalize_test();
  return ret;
}
<file_sep># -*- coding: utf-8 -*-
"""
    pygeodiff
    -----------
    This module provides tools for create diffs of geospatial data formats
    :copyright: (c) 2019-2022 Lutra Consulting Ltd.
    :license: MIT, see LICENSE for more details.
"""

from .main import GeoDiff
from .geodifflib import (
    GeoDiffLibError,
    GeoDiffLibConflictError,
    GeoDiffLibUnsupportedChangeError,
    GeoDiffLibVersionError,
    ChangesetEntry,
    ChangesetReader,
    UndefinedValue,
)
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2022 <NAME>
*/

#include "gtest/gtest.h"
#include "geodiff_testutils.hpp"
#include "geodiff.h"
#include "geodiffutils.hpp"

TEST( SkipTablesSqlite3Test, test_skip_create )
{
  std::string testname( "test_skip_create" );
  std::string base = pathjoin( testdir(), "skip_tables", "base.gpkg" );
  std::string modified_all = pathjoin( testdir(), "skip_tables", "modified_all.gpkg" );
  std::string modified_points = pathjoin( testdir(), "skip_tables", "modified_points.gpkg" );
  std::string changeset_points = pathjoin( tmpdir(), testname, "changeset_points.bin" );
  std::string patched_points = pathjoin( tmpdir(), testname, "patched_points.gpkg" );

  makedir( pathjoin( tmpdir(), testname ) );

  // ignore lines table when creating changeset
  std::vector<std::string> tablesToSkip( { "lines" } );
  Context *ctx = static_cast<Context *>( testContext() );
  ctx->setTablesToSkip( tablesToSkip );

  int res = GEODIFF_createChangeset( testContext(), base.c_str(), modified_all.c_str(), changeset_points.c_str() );
  EXPECT_EQ( res, GEODIFF_SUCCESS );

  int nchanges = GEODIFF_changesCount( testContext(), changeset_points.c_str() );
  EXPECT_EQ( nchanges, 4 );

  // reconstuct changes in the points layer
  filecopy( patched_points, base );
  res = GEODIFF_applyChangeset( testContext(), patched_points.c_str(), changeset_points.c_str() );
  EXPECT_EQ( res, GEODIFF_SUCCESS );

  // check that now it is same file with modified
  EXPECT_TRUE( equals( patched_points, modified_points, false ) );

  // reset skip list
  tablesToSkip.clear();
  ctx->setTablesToSkip( tablesToSkip );
}

TEST( SkipTablesSqlite3Test, test_skip_apply )
{
  std::string testname( "test_skip_apply" );
  std::string base = pathjoin( testdir(), "skip_tables", "base.gpkg" );
  std::string modified_all = pathjoin( testdir(), "skip_tables", "modified_all.gpkg" );
  std::string modified_points = pathjoin( testdir(), "skip_tables", "modified_points.gpkg" );
  std::string changeset = pathjoin( tmpdir(), testname, "changeset.bin" );
  std::string patched_points = pathjoin( tmpdir(), testname, "patched_points.gpkg" );

  makedir( pathjoin( tmpdir(), testname ) );

  int res = GEODIFF_createChangeset( testContext(), base.c_str(), modified_all.c_str(), changeset.c_str() );
  EXPECT_EQ( res, GEODIFF_SUCCESS );

  int nchanges = GEODIFF_changesCount( testContext(), changeset.c_str() );
  EXPECT_EQ( nchanges, 6 );

  // ignore lines table when applying changeset
  std::vector<std::string> tablesToSkip( { "lines" } );
  Context *ctx = static_cast<Context *>( testContext() );
  ctx->setTablesToSkip( tablesToSkip );

  // reconstuct changes in the points layer
  filecopy( patched_points, base );
  res = GEODIFF_applyChangeset( testContext(), patched_points.c_str(), changeset.c_str() );
  EXPECT_EQ( res, GEODIFF_SUCCESS );

  // check that now it is same file with modified
  EXPECT_TRUE( equals( patched_points, modified_points, false ) );

  // reset skip list
  tablesToSkip.clear();
  ctx->setTablesToSkip( tablesToSkip );
}

int main( int argc, char **argv )
{
  testing::InitGoogleTest( &argc, argv );
  init_test();
  int ret =  RUN_ALL_TESTS();
  finalize_test();
  return ret;
}
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef TABLESCHEMA_H
#define TABLESCHEMA_H

#include <string>
#include <vector>
#include <algorithm>

#include "geodiffutils.hpp"

/* Information about column type, converted to base type */
struct TableColumnType
{
  enum BaseType
  {
    TEXT = 0,
    INTEGER,
    DOUBLE,
    BOOLEAN,
    BLOB,
    GEOMETRY,
    DATE,
    DATETIME
  };

  BaseType baseType = TEXT;
  std::string dbType;

  //! Returns string representation of the base type enum value
  static std::string baseTypeToString( BaseType t );

  //! Unified conversion of GeoPackage and PostgreSQL types to base types
  void convertToBaseType();

  bool operator==( const std::string &other ) const
  {
    return ( dbType == other );
  }

  bool operator!=( const std::string &other ) const
  {
    return !( *this == other );
  }

  bool operator==( const TableColumnType &other ) const
  {
    return ( dbType == other.dbType );
  }

  bool operator!=( const TableColumnType &other ) const
  {
    return !( *this == other );
  }

  bool operator==( const TableColumnType::BaseType &other ) const
  {
    return ( baseType == other );
  }

  bool operator!=( const TableColumnType::BaseType &other ) const
  {
    return !( *this == other );
  }
};

/** Information about a single column of a database table */
struct TableColumnInfo
{
  //! Unique name of the column
  std::string name;
  //! Type of the column as reported by the database
  TableColumnType type;
  //! Whether this column is a part of the table's primary key
  bool isPrimaryKey = false;

  //! Whether the column is defined as "NOT NULL" - i.e. null values are not allowed
  bool isNotNull = false;
  //! Whether the column has a default value assigned as an auto-incrementing
  bool isAutoIncrement = false;

  //! Whether the column encodes geometry data
  bool isGeometry = false;
  //! In case of geometry column - contains geometry type (e.g. POINT / LINESTRING / POLYGON / ...)
  //! Only "flat" types are allowed - without Z / M - these are stored separately in geomHasZ, geomHasM
  std::string geomType;
  //! In case of geometry column - contains ID of the spatial ref. system
  int geomSrsId = -1;
  //! Whether the geometry column includes Z coordinates
  bool geomHasZ = false;
  //! Whether the geometry column includes M coordinates
  bool geomHasM = false;

  bool compareWithBaseTypes( TableColumnInfo other ) const
  {
    return name == other.name && type == other.type.baseType && isPrimaryKey == other.isPrimaryKey &&
           isNotNull == other.isNotNull && isAutoIncrement == other.isAutoIncrement &&
           isGeometry == other.isGeometry && geomType == other.geomType && geomSrsId == other.geomSrsId &&
           geomHasZ == other.geomHasZ && geomHasM == other.geomHasM;
  }

  std::string dump() const
  {
    std::string output = name + " | " + type.dbType + " | ";
    if ( isPrimaryKey )
      output += "pkey ";
    if ( isNotNull )
      output += "notnull ";
    if ( isAutoIncrement )
      output += "autoincrement";
    if ( isGeometry )
    {
      output += "geometry:" + geomType + ":" + std::to_string( geomSrsId );
      if ( geomHasZ )
        output += "hasZ";
      if ( geomHasM )
        output += "hasM";
    }
    return output;
  }

  void setGeometry( const std::string &geomTypeName, int srsId, bool hasM, bool hasZ )
  {
    type.baseType = TableColumnType::GEOMETRY;
    isGeometry = true;
    geomType = geomTypeName;
    geomSrsId = srsId;
    geomHasM = hasM;
    geomHasZ = hasZ;
  }

  bool operator==( const TableColumnInfo &other ) const
  {
    return name == other.name && type == other.type && isPrimaryKey == other.isPrimaryKey &&
           isNotNull == other.isNotNull && isAutoIncrement == other.isAutoIncrement &&
           isGeometry == other.isGeometry && geomType == other.geomType && geomSrsId == other.geomSrsId &&
           geomHasZ == other.geomHasZ && geomHasM == other.geomHasM;
  }

  bool operator!=( const TableColumnInfo &other ) const
  {
    return !( *this == other );
  }

};


/** Definition of a coordinate reference system - may be needed when creating tables */
struct CrsDefinition
{
  int srsId = 0;            //!< Identifier of the CRS within the database
  std::string authName;     //!< Name of the authority (usually "EPSG")
  int authCode = 0;         //!< Code of the CRS within authority
  std::string wkt;          //!< Definition in form of WKT string

  bool operator==( const CrsDefinition &other ) const
  {
    // TODO: We should also compare crs in future, however, the format may vary even for the same object
    // and that makes it hard to compare just with string compare
    return srsId == other.srsId && authName == other.authName && authCode == other.authCode;
  }
  bool operator!=( const CrsDefinition &other ) const
  {
    return !( *this == other );
  }

};


/** Information about table's spatial extent */
struct Extent
{
  Extent( double _minX = 0, double _minY = 0, double _maxX = 0, double _maxY = 0 )
    : minX( _minX ), minY( _minY ), maxX( _maxX ), maxY( _maxY ) {}

  double minX = 0, minY = 0, maxX = 0, maxY = 0;
};


/** Information about table schema of a database table */
struct TableSchema
{
  std::string name;
  std::vector<TableColumnInfo> columns;
  CrsDefinition crs;

  //! Returns true if at least one column is a part of table's primary key
  bool hasPrimaryKey() const;

  //! Returns column index for the given column name (returns SIZE_MAX if column not is not found)
  size_t columnFromName( const std::string &columnName ) const;

  //! Returns index of the first encountered geometry column (returns SIZE_MAX if no geometry column is found)
  size_t geometryColumn() const;

  std::string dump() const
  {
    std::string output = "TABLE " + name + "\n";
    for ( const TableColumnInfo &col : columns )
    {
      // cppcheck-suppress useStlAlgorithm
      output += "  " + col.dump() + "\n";
    }
    return output;
  }

  bool compareWithBaseTypes( const TableSchema &other ) const
  {
    return name == other.name &&
           crs == other.crs && columns.size() == other.columns.size() &&
           std::equal( columns.begin(), columns.end(), other.columns.begin(),
    []( const TableColumnInfo & me, const TableColumnInfo & other ) { return me.compareWithBaseTypes( other ); } );
  }

  bool operator==( const TableSchema &other ) const
  {
    return name == other.name && columns == other.columns && crs == other.crs;
  }
  bool operator!=( const TableSchema &other ) const
  {
    return !( *this == other );
  }
};

//! Converts column name to base type and returns struct with both names
TableColumnType columnType( const Context *context, const std::string &columnType, const std::string &driverName, bool isGeometry = false );

//! Converts table schema from base to destination driver, raises GeoDiffException if that is not supported
void tableSchemaConvert( const std::string &driverDstName, TableSchema &tbl );

#endif // TABLESCHEMA_H
<file_sep>#ifndef BASE64UTILS_H
#define BASE64UTILS_H

#include <string>

std::string base64_encode( unsigned char const *bytes_to_encode, unsigned int in_len );
std::string base64_decode( std::string const &encoded_string );


#endif // BASE64UTILS_H
<file_sep>
DROP SCHEMA IF EXISTS gd_inserted_1_b CASCADE;

CREATE SCHEMA gd_inserted_1_b;

CREATE TABLE gd_inserted_1_b.simple ( "fid" SERIAL PRIMARY KEY, "geometry" GEOMETRY(POINT, 4326), "name" TEXT, "rating" INTEGER);

INSERT INTO gd_inserted_1_b.simple VALUES (1, ST_GeomFromText('Point (-1.08891928864569065 0.46101231190150482)', 4326), 'feature1', 1);
INSERT INTO gd_inserted_1_b.simple VALUES (2, ST_GeomFromText('Point (-0.36388508891928861 0.56224350205198359)', 4326), 'feature2', 2);
INSERT INTO gd_inserted_1_b.simple VALUES (3, ST_GeomFromText('Point (-0.73050615595075241 0.04240766073871405)', 4326), 'feature3', 3);
INSERT INTO gd_inserted_1_b.simple VALUES (4, ST_GeomFromText('Point (-0.83257081666079680 0.16881887690991337)', 4326), 'my new point B', 2);
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef CHANGESET_H
#define CHANGESET_H

#include <assert.h>
#include <memory>
#include <string>
#include <vector>


/**
 * Representation of a single value stored in a column.
 * It can be one of types:
 * - NULL
 * - integer
 * - double
 * - string
 * - binary data (blob)
 *
 * There is also a special "undefined" value type which is different
 * from "null". The "undefined" value means that the particular value
 * has not changed, for example in UPDATE change if a column's value
 * is unchanged, its value will have this type.
 */
struct Value
{
    Value() {}
    ~Value() { reset(); }

    Value( const Value &other )
    {
      *this = other;
    }

    Value &operator=( const Value &other )
    {
      if ( &other != this )
      {
        reset();
        mType = other.mType;
        mVal = other.mVal;
        if ( mType == TypeText || mType == TypeBlob )
        {
          mVal.str = new std::string( *mVal.str ); // make a deep copy
        }
      }
      return *this;
    }

    bool operator==( const Value &other ) const
    {
      if ( mType != other.mType )
        return false;
      if ( mType == TypeUndefined || mType == TypeNull )
        return true;
      if ( mType == TypeInt )
        return getInt() == other.getInt();
      if ( mType == TypeDouble )
        return getDouble() == other.getDouble();
      if ( mType == TypeText || mType == TypeBlob )
        return getString() == other.getString();

      assert( false );
      return false;
    }

    bool operator!=( const Value &other ) const
    {
      return !( *this == other );
    }

    //! Possible value types
    enum Type
    {
      TypeUndefined = 0,   //!< equal to "undefined" value type in sqlite3 session extension
      TypeInt       = 1,   //!< equal to SQLITE_INTEGER
      TypeDouble    = 2,   //!< equal to SQLITE_FLOAT
      TypeText      = 3,   //!< equal to SQLITE_TEXT
      TypeBlob      = 4,   //!< equal to SQLITE_BLOB
      TypeNull      = 5,   //!< equal to SQLITE_NULL
    };

    Type type() const { return mType; }

    int64_t getInt() const
    {
      assert( mType == TypeInt );
      return mVal.num_i;
    }
    double getDouble() const
    {
      assert( mType == TypeDouble );
      return mVal.num_f;
    }
    const std::string &getString() const
    {
      assert( mType == TypeText || mType == TypeBlob );
      return *mVal.str;
    }

    void setInt( int64_t n )
    {
      reset();
      mType = TypeInt;
      mVal.num_i = n;
    }
    void setDouble( double n )
    {
      reset();
      mType = TypeDouble;
      mVal.num_f = n;
    }
    void setString( Type t, const char *ptr, size_t size )
    {
      reset();
      assert( t == TypeText || t == TypeBlob );
      mType = t;
      mVal.str = new std::string( ptr, size );
    }
    void setUndefined()
    {
      reset();
    }
    void setNull()
    {
      reset();
      mType = TypeNull;
    }

    static Value makeInt( int64_t n ) { Value v; v.setInt( n ); return v; }
    static Value makeDouble( double n ) { Value v; v.setDouble( n ); return v; }
    static Value makeText( const std::string &s ) { Value v; v.setString( TypeText, s.data(), s.size() ); return v; }
    static Value makeNull() { Value v; v.setNull(); return v; }

  protected:
    void reset()
    {
      if ( mType == TypeText || mType == TypeBlob )
      {
        delete mVal.str;
      }
      mType = TypeUndefined;
    }

  protected:
    Type mType = TypeUndefined;
    union
    {
      int64_t num_i;
      double num_f;
      std::string *str;
    } mVal = {0};

};


//! std::hash<Value> implementation
namespace std
{
  template<> struct hash<Value>
  {
    std::size_t operator()( const Value &v ) const
    {
      switch ( v.type() )
      {
        case Value::TypeUndefined:
          return 0xcccccccc;
        case Value::TypeInt:
          return std::hash<int64_t> {}( v.getInt() );
        case Value::TypeDouble:
          return std::hash<double> {}( v.getDouble() );
        case Value::TypeText:
        case Value::TypeBlob:
          return std::hash<std::string> {}( v.getString() );
        case Value::TypeNull:
          return 0xdddddddd;
      }
      assert( false );
      return 0;
    }
  };
}


/**
 * Table metadata stored in changeset file
 */
struct ChangesetTable
{
  //! Name of the table
  std::string name;
  //! Array of true/false values (one for each column) - indicating whether particular column is a part of primary key
  std::vector<bool> primaryKeys;

  //! Returns number of columns
  size_t columnCount() const { return primaryKeys.size(); }
};


/**
 * Details of a single change within a changeset
 *
 * Contents of old/new values array based on operation type:
 * - INSERT - new values contain data of the row to be inserted, old values array is invalid
 * - DELETE - old values contain data of the row to be deleted, new values array is invalid
 * - UPDATE - both old and new values arrays are valid, if a column has not changed, both
 *            old and new value have "undefined" value type. In addition to that, primary key
 *            columns of old value are always present (but new value of pkey columns is undefined
 *            if the primary key is not being changed).
 */
struct ChangesetEntry
{
  enum OperationType
  {
    OpInsert = 18,  //!< equal to SQLITE_INSERT
    OpUpdate = 23,  //!< equal to SQLITE_UPDATE
    OpDelete = 9,   //!< equal to SQLITE_DELETE
  };

  //! Type of the operation in this entry
  OperationType op;
  //! Column values for "old" record - only valid for UPDATE and DELETE
  std::vector<Value> oldValues;
  //! Column values for "new" record - only valid for UPDATE and INSERT
  std::vector<Value> newValues;
  /**
   * Optional pointer to the source table information as stored in changeset.
   *
   * When the changeset entry has been read by ChangesetReader, the table always will be set to a valid
   * instance. Do not delete the instance - it is owned by ChangesetReader.
   *
   * When the changeset entry is being passed to ChangesetWriter, the table pointer is ignored
   * and it does not need to be set (writer has an explicit beginTable() call to set table).
   */
  ChangesetTable *table = nullptr;

  //! a quick way for tests to create a changeset entry
  static ChangesetEntry make( ChangesetTable *t, OperationType o, const std::vector<Value> &oldV, const std::vector<Value> &newV )
  {
    ChangesetEntry e;
    e.op = o;
    e.oldValues = oldV;
    e.newValues = newV;
    e.table = t;
    return e;
  }
};

#endif // CHANGESET_H
<file_sep>DROP SCHEMA IF EXISTS gd_pg_diff CASCADE;
CREATE SCHEMA gd_pg_diff;

CREATE TABLE gd_pg_diff.simple (
    "fid" SERIAL PRIMARY KEY,
    "geometry" GEOMETRY(POINT, 4326),
    "name" text
);

INSERT INTO gd_pg_diff.simple (
    "fid",
    "geometry",
    "name"
)
VALUES (
    1,
    ST_GeomFromText('Point (0 1)', 4326),
    'feature 1'
);

INSERT INTO gd_pg_diff.simple (
    "fid",
    "geometry",
    "name"
)
VALUES (
    2,
    ST_GeomFromText('Point (1 2)', 4326),
    'feature 2'
);
<file_sep># -*- coding: utf-8 -*-
"""
    :copyright: (c) 2023 <NAME>
    :license: MIT, see LICENSE for more details.
"""

from .testutils import *
import os
import shutil


class UnitTestsCliCalls(GeoDiffCliTests):
    """Some quick tests of various CLI commands just to make sure they are not broken"""

    def test_cli_calls(self):
        print("********************************************************")
        print("PYTHON: test API calls")

        outdir = create_dir("cli-calls")

        print("-- invalid")
        self.run_command([], expect_fail=True)
        self.run_command(["badcommand"], expect_fail=True)

        print("-- dump")
        self.run_command(["dump"], expect_fail=True)
        self.run_command(["dump", "--driver"], expect_fail=True)
        self.run_command(["dump", "--driver", "sqlite"], expect_fail=True)
        self.run_command(["dump", geodiff_test_dir() + "/base.gpkg"], expect_fail=True)
        self.run_command(
            [
                "dump",
                geodiff_test_dir() + "/base.gpkg",
                outdir + "/dump2.diff",
                "extra_arg",
            ],
            expect_fail=True,
        )
        self.run_command(
            ["dump", geodiff_test_dir() + "/base.gpkg", outdir + "/dump.diff"]
        )

        print("-- as-json")
        self.run_command(["as-json"], expect_fail=True)
        self.run_command(["as-json", "arg1", "extra_arg"], expect_fail=True)
        self.run_command(["as-json", outdir + "/dump.diff"], check_in_output="feature2")
        self.run_command(["as-json", outdir + "/dump.diff", outdir + "/dump.json"])
        self.run_command(
            ["as-json", outdir + "/dump.diff", outdir + "/dump.json", "extra_arg"],
            expect_fail=True,
        )
        file_contains(outdir + "/dump.json", "feature3")

        print("-- as-summary")
        self.run_command(["as-summary"], expect_fail=True)
        self.run_command(["as-summary", "arg1", "extra_arg"], expect_fail=True)
        self.run_command(
            ["as-summary", outdir + "/dump.diff"], check_in_output="geodiff_summary"
        )
        self.run_command(["as-summary", outdir + "/dump.diff", outdir + "/dump.json"])
        file_contains(outdir + "/dump.json", "geodiff_summary")

        print("-- diff")
        self.run_command(["diff"], expect_fail=True)
        self.run_command(["diff", "--driver1"], expect_fail=True)
        self.run_command(["diff", "--driver1", "sqlite"], expect_fail=True)
        self.run_command(["diff", "--skip-tables"], expect_fail=True)
        self.run_command(
            ["diff", geodiff_test_dir() + "/non-existent.gpkg"], expect_fail=True
        )
        self.run_command(
            [
                "diff",
                geodiff_test_dir() + "/non-existent.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "diff",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
                outdir + "/diff.diff",
                "extra_arg",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "diff",
                "--json",
                "--summary",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "diff",
                "--driver1",
                "sqlite",
                "''",
                "--driver2",
                "sqlite",
                "''",
                "--summary",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            check_in_output="geodiff_summary",
            expect_fail=True,
        )
        self.run_command(
            [
                "diff",
                "--driver1",
                "sqlite",
                "--driver2",
                "sqlite",
                "''",
                "--summary",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            check_in_output="geodiff_summary",
            expect_fail=True,
        )
        self.run_command(
            [
                "diff",
                "--json",
                "--skip-tables",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            check_in_output="geodiff",
            expect_fail=True,
        )

        self.run_command(
            [
                "diff",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ]
        )
        self.run_command(
            [
                "diff",
                "--json",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            check_in_output="update",
        )
        self.run_command(
            [
                "diff",
                "--json",
                "--skip-tables",
                "simple",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            check_in_output="geodiff",
        )  # empty diff
        self.run_command(
            [
                "diff",
                "--summary",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            check_in_output="geodiff_summary",
        )
        self.run_command(
            [
                "diff",
                "--driver",
                "sqlite",
                "''",
                "--summary",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            check_in_output="geodiff_summary",
        )
        self.run_command(
            [
                "diff",
                "--driver-1",
                "sqlite",
                "''",
                "--driver-2",
                "sqlite",
                "''",
                "--summary",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            check_in_output="geodiff_summary",
        )
        self.run_command(
            [
                "diff",
                geodiff_test_dir() + "/base.gpkg",
                geodiff_test_dir() + "/2_inserts/inserted_1_A.gpkg",
                outdir + "/diff.diff",
            ]
        )

        print("-- copy")
        self.run_command(["copy"], expect_fail=True)
        self.run_command(
            ["copy", geodiff_test_dir() + "/non-existent.gpkg", outdir + "/copy.gpkg"],
            expect_fail=True,
        )
        self.run_command(
            [
                "copy",
                geodiff_test_dir() + "/base.gpkg",
                outdir + "/copy.gpkg",
                "extra_arg",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "copy",
                "--skip-tables",
                geodiff_test_dir() + "/base.gpkg",
                outdir + "/copy.gpkg",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "copy",
                "--driver-1",
                geodiff_test_dir() + "/base.gpkg",
                outdir + "/copy.gpkg",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "copy",
                "--driver-2",
                geodiff_test_dir() + "/base.gpkg",
                outdir + "/copy.gpkg",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "copy",
                "--driver",
                "sqlite",
                "''",
                "--skip-tables",
                "unknown",
                geodiff_test_dir() + "/base.gpkg",
                outdir + "/copy2.gpkg",
            ],
            expect_fail=True,
        )

        self.run_command(
            ["copy", geodiff_test_dir() + "/base.gpkg", outdir + "/copy.gpkg"]
        )

        print("-- apply")
        self.run_command(
            ["copy", geodiff_test_dir() + "/base.gpkg", outdir + "/copyA.gpkg"]
        )
        self.run_command(
            ["copy", geodiff_test_dir() + "/base.gpkg", outdir + "/copyB.gpkg"]
        )

        self.run_command(["apply"], expect_fail=True)
        self.run_command(["apply", "--driver"], expect_fail=True)
        self.run_command(["apply", "--skip-tables"], expect_fail=True)
        self.run_command(["apply", outdir + "/copyA.gpkg"], expect_fail=True)
        self.run_command(
            [
                "apply",
                outdir + "/copyA.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            expect_fail=True,
        )  # second arg is diff
        self.run_command(
            ["apply", outdir + "/copyA.gpkg", outdir + "/diff.diff", "extra_arg"],
            expect_fail=True,
        )
        self.run_command(
            ["apply", outdir + "/copyA.gpkg", outdir + "/diff.diff", "extra_arg"],
            expect_fail=True,
        )
        self.run_command(
            [
                "apply",
                "--driver",
                outdir + "/copyA.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "apply",
                "--driver",
                "sqlite",
                outdir + "/copyA.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "apply",
                "--skip-tables",
                outdir + "/copyA.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "apply",
                "--invalid-flag",
                outdir + "/copyA.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
            ],
            expect_fail=True,
        )

        self.run_command(
            [
                "apply",
                "--driver",
                "sqlite",
                "''",
                "--skip-tables",
                "''",
                outdir + "/copyA.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
            ]
        )
        self.run_command(
            [
                "apply",
                outdir + "/copyB.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
            ]
        )

        print("-- rebase-diff")
        self.run_command(
            ["copy", geodiff_test_dir() + "/base.gpkg", outdir + "/copyF.gpkg"]
        )

        self.run_command(["rebase-diff"], expect_fail=True)
        self.run_command(["rebase-diff", "--driver"], expect_fail=True)
        self.run_command(["rebase-diff", "--driver", "sqlite"], expect_fail=True)
        self.run_command(["rebase-diff", outdir + "/copyF.gpkg"], expect_fail=True)
        self.run_command(
            [
                "rebase-diff",
                outdir + "/copyF.gpkg",
                geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            ],
            expect_fail=True,
        )  # second arg is diff
        self.run_command(
            [
                "rebase-diff",
                outdir + "/copyF.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
                geodiff_test_dir() + "/2_updates/base-updated_A.diff",
                outdir + "/rebase-diff.diff",
                outdir + "/confF.confict",
                "extra_arg",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "rebase-diff",
                outdir + "/copyF.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
                geodiff_test_dir() + "/bad.diff",
                outdir + "/rebase-diff.diff",
                outdir + "/confF.confict",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "rebase-diff",
                outdir + "/copyF.gpkg",
                geodiff_test_dir() + "/bad.diff",
                geodiff_test_dir() + "/2_updates/base-updated_A.diff",
                outdir + "/rebase-diff.diff",
                outdir + "/confF.confict",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "rebase-diff",
                outdir + "/copyF.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
                geodiff_test_dir() + "/2_updates/base-updated_A.diff",
                outdir + "/rebase-diff.diff",
            ],
            expect_fail=True,
        )

        self.run_command(
            [
                "rebase-diff",
                outdir + "/copyF.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
                geodiff_test_dir() + "/2_updates/base-updated_A.diff",
                outdir + "/rebase-diff.diff",
                outdir + "/confF.confict",
            ]
        )

        print("-- rebase-db")
        self.run_command(
            ["copy", geodiff_test_dir() + "/base.gpkg", outdir + "/copyD.gpkg"]
        )

        self.run_command(["rebase-db"], expect_fail=True)
        self.run_command(["rebase-db", "--driver"], expect_fail=True)
        self.run_command(["rebase-db", "--driver", "sqlite"], expect_fail=True)
        self.run_command(["rebase-db", "--bad_flag"], expect_fail=True)
        self.run_command(
            [
                "rebase-db",
                geodiff_test_dir() + "/base.gpkg",
                outdir + "/copyD.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
            ],
            expect_fail=True,
        )  # missing arg
        self.run_command(
            [
                "rebase-db",
                geodiff_test_dir() + "/bad.gpkg",
                outdir + "/copyD.gpkg",
                geodiff_test_dir() + "/bad.diff",
                outdir + "/rebasedb.conflicts.json",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "rebase-db",
                geodiff_test_dir() + "/base.gpkg",
                outdir + "/copyD.gpkg",
                geodiff_test_dir() + "/bad.diff",
                outdir + "/rebasedb.conflicts.json",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "rebase-db",
                geodiff_test_dir() + "/base.gpkg",
                outdir + "/bad.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
                outdir + "/rebasedb.conflicts.json",
            ],
            expect_fail=True,
        )

        self.run_command(
            [
                "rebase-db",
                geodiff_test_dir() + "/base.gpkg",
                outdir + "/copyD.gpkg",
                geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
                outdir + "/rebasedb.conflicts.json",
            ]
        )

        print("-- invert")
        self.run_command(["invert"], expect_fail=True)
        self.run_command(["invert", "--bad_flag"], expect_fail=True)
        self.run_command(
            ["invert", geodiff_test_dir() + "/concat/bar-insert.diff"], expect_fail=True
        )
        self.run_command(
            [
                "invert",
                geodiff_test_dir() + "/concat/bar-insert.diff",
                outdir + "/invert.diff",
                "extra_arg",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "invert",
                geodiff_test_dir() + "/concat/non-existent-file.diff",
                outdir + "/invert.diff",
            ],
            expect_fail=True,
        )

        self.run_command(
            [
                "invert",
                geodiff_test_dir() + "/concat/bar-insert.diff",
                outdir + "/invert.diff",
            ]
        )
        self.run_command(["as-json", outdir + "/invert.diff"], check_in_output="points")

        print("-- concat")
        self.run_command(["concat"], expect_fail=True)
        self.run_command(
            [
                "concat",
                geodiff_test_dir() + "/concat/non-existent-file.diff",
                geodiff_test_dir() + "/concat/bar-update.diff",
                outdir + "/concat-fail.diff",
            ],
            expect_fail=True,
        )
        self.run_command(
            [
                "concat",
                geodiff_test_dir() + "/concat/bar-insert.diff",
                geodiff_test_dir() + "/concat/bar-update.diff",
                outdir + "/concat2.diff",
            ]
        )
        self.run_command(
            ["as-json", outdir + "/concat2.diff"], check_in_output="MODIFIED"
        )
        self.run_command(
            [
                "concat",
                geodiff_test_dir() + "/concat/bar-insert.diff",
                geodiff_test_dir() + "/concat/bar-update.diff",
                geodiff_test_dir() + "/concat/bar-delete.diff",
                outdir + "/concat3.diff",
            ]
        )
        self.run_command(
            ["as-json", outdir + "/concat3.diff"], check_in_output="geodiff"
        )  # empty file

        print("-- schema")
        self.run_command(["schema"], expect_fail=True)
        self.run_command(
            ["schema", geodiff_test_dir() + "/non-existent.gpkg"], expect_fail=True
        )
        self.run_command(
            ["schema", geodiff_test_dir() + "/base.gpkg"], check_in_output="MEDIUMINT"
        )
        self.run_command(
            ["schema", geodiff_test_dir() + "/base.gpkg", outdir + "/schema.txt"]
        )
        file_contains(outdir + "/schema.txt", "MEDIUMINT")
        self.run_command(
            [
                "schema",
                geodiff_test_dir() + "/base.gpkg",
                outdir + "/schema-fail.txt",
                "extra_arg",
            ],
            expect_fail=True,
        )

        print("-- drivers")
        self.run_command(["drivers"], check_in_output="sqlite")
        self.run_command(["drivers", "extra_arg"], expect_fail=True)

        print("-- version")
        self.run_command(["version"], check_in_output=".")
        self.run_command(["version", "extra_arg"], expect_fail=True)

        print("-- help")
        self.run_command(["help"], check_in_output="Lutra Consulting")
        self.run_command(["help", "extra_arg"], expect_fail=True)
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef CHANGESETWRITER_H
#define CHANGESETWRITER_H

#include "geodiff.h"

#include "changeset.h"

#include <fstream>

/**
 * Class for writing binary changeset files.
 * First use open() to create a new changeset file and then for each modified table:
 * - call beginTable() once
 * - then call writeEntry() for each change within that table
 *
 * See changeset-format.md for the documentation of the format.
 */
class ChangesetWriter
{
  public:

    /**
     *  opens a file for writing changeset (will overwrite if it exists already)
     *  throws GeoDiffException on error
     */
    void open( const std::string &filename );

    //! writes table information, all subsequent writes will be related to this table until next call to beginTable()
    void beginTable( const ChangesetTable &table );

    //! writes table change entry
    void writeEntry( const ChangesetEntry &entry );

  private:

    void writeByte( char c );
    void writeVarint( int n );
    void writeNullTerminatedString( const std::string &str );

    void writeRowValues( const std::vector<Value> &values );

    std::ofstream mFile;

    ChangesetTable mCurrentTable;  // currently processed table
};

#endif // CHANGESETWRITER_H
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#include "gtest/gtest.h"
#include "geodiff_testutils.hpp"
#include "geodiff.h"

#include "changesetreader.h"

TEST( ChangesetReaderTest, test_open )
{
  std::string changeset = "invalid_file";

  ChangesetReader reader;
  EXPECT_FALSE( reader.open( changeset ) );
}

TEST( ChangesetReaderTest, test_read_insert )
{
  std::string changeset = pathjoin( testdir(), "2_inserts", "base-inserted_1_A.diff" );

  ChangesetReader reader;
  EXPECT_TRUE( reader.open( changeset ) );

  ChangesetEntry entry;
  EXPECT_TRUE( reader.nextEntry( entry ) );
  EXPECT_EQ( entry.op, ChangesetEntry::OpInsert );
  EXPECT_EQ( entry.table->name, "simple" );
  EXPECT_EQ( entry.table->primaryKeys.size(), 4 );
  EXPECT_EQ( entry.table->primaryKeys[0], true );
  EXPECT_EQ( entry.table->primaryKeys[1], false );
  EXPECT_EQ( entry.newValues.size(), 4 );
  EXPECT_EQ( entry.newValues[0].type(), Value::TypeInt );
  EXPECT_EQ( entry.newValues[0].getInt(), 4 );
  EXPECT_EQ( entry.newValues[1].type(), Value::TypeBlob );
  EXPECT_EQ( entry.newValues[2].type(), Value::TypeText );
  EXPECT_EQ( entry.newValues[2].getString(), "my new point A" );

  EXPECT_FALSE( reader.nextEntry( entry ) );
  EXPECT_FALSE( reader.nextEntry( entry ) );
}

TEST( ChangesetReaderTest, test_read_update )
{
  std::string changeset = pathjoin( testdir(), "2_updates", "base-updated_A.diff" );

  ChangesetReader reader;
  EXPECT_TRUE( reader.open( changeset ) );

  ChangesetEntry entry;
  EXPECT_TRUE( reader.nextEntry( entry ) );
  EXPECT_EQ( entry.op, ChangesetEntry::OpUpdate );
  EXPECT_EQ( entry.table->name, "simple" );

  EXPECT_EQ( entry.oldValues.size(), 4 );
  EXPECT_EQ( entry.newValues.size(), 4 );
  // pkey - unchanged
  EXPECT_EQ( entry.oldValues[0].type(), Value::TypeInt );
  EXPECT_EQ( entry.oldValues[0].getInt(), 2 );
  EXPECT_EQ( entry.newValues[0].type(), Value::TypeUndefined );
  // geometry - changed
  EXPECT_EQ( entry.oldValues[1].type(), Value::TypeBlob );
  EXPECT_EQ( entry.newValues[1].type(), Value::TypeBlob );
  // unchanged
  EXPECT_EQ( entry.oldValues[2].type(), Value::TypeUndefined );
  EXPECT_EQ( entry.newValues[2].type(), Value::TypeUndefined );
  // changed
  EXPECT_EQ( entry.oldValues[3].type(), Value::TypeInt );
  EXPECT_EQ( entry.oldValues[3].getInt(), 2 );
  EXPECT_EQ( entry.newValues[3].type(), Value::TypeInt );
  EXPECT_EQ( entry.newValues[3].getInt(), 9999 );

  EXPECT_FALSE( reader.nextEntry( entry ) );
}

TEST( ChangesetReaderTest, test_read_delete )
{
  std::string changeset = pathjoin( testdir(), "2_deletes", "base-deleted_A.diff" );

  ChangesetReader reader;
  EXPECT_TRUE( reader.open( changeset ) );

  ChangesetEntry entry;
  EXPECT_TRUE( reader.nextEntry( entry ) );
  EXPECT_EQ( entry.op, ChangesetEntry::OpDelete );
  EXPECT_EQ( entry.table->name, "simple" );

  EXPECT_EQ( entry.oldValues.size(), 4 );
  EXPECT_EQ( entry.oldValues[0].type(), Value::TypeInt );
  EXPECT_EQ( entry.oldValues[0].getInt(), 2 );
  EXPECT_EQ( entry.oldValues[1].type(), Value::TypeBlob );
  EXPECT_EQ( entry.oldValues[2].type(), Value::TypeText );
  EXPECT_EQ( entry.oldValues[2].getString(), "feature2" );
  EXPECT_EQ( entry.oldValues[3].type(), Value::TypeInt );
  EXPECT_EQ( entry.oldValues[3].getInt(), 2 );

  EXPECT_FALSE( reader.nextEntry( entry ) );
  EXPECT_FALSE( reader.nextEntry( entry ) );
}


int main( int argc, char **argv )
{
  testing::InitGoogleTest( &argc, argv );
  init_test();
  int ret =  RUN_ALL_TESTS();
  finalize_test();
  return ret;
}
<file_sep>
DROP SCHEMA IF EXISTS gd_test_apply CASCADE;

CREATE SCHEMA gd_test_apply;

ALTER TABLE gd_base.simple SET SCHEMA gd_test_apply;
<file_sep>
DROP SCHEMA IF EXISTS gd_datatypes CASCADE;
DROP SCHEMA IF EXISTS gd_datatypes_copy CASCADE;

CREATE SCHEMA gd_datatypes;

CREATE TABLE gd_datatypes.simple (
    "fid" SERIAL PRIMARY KEY,
    "geometry" GEOMETRY(POINT, 4326),
    "name_text" text,
    "name_varchar" character varying,
    "name_varchar_len" character varying(50),
    "name_char_len" character(100),
    "feature_id" uuid DEFAULT uuid_generate_v4(),
    "col_numeric" numeric(10,3),
    "col_decimal" decimal(10,3)
);

INSERT INTO gd_datatypes.simple (
    "fid",
    "geometry",
    "name_text",
    "name_varchar",
    "name_varchar_len",
    "name_char_len",
    "col_numeric",
    "col_decimal"
)
VALUES (
    1,
    ST_GeomFromText('Point (-1.08891928864569065 0.46101231190150482)', 4326),
    'feature1',
    'feature1 varchar',
    'feature1 varchar(50)',
    'feature1 char(100)',
    31.203,
    13.302
);
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef GEODIFFLOGGER_H
#define GEODIFFLOGGER_H

#include <string>
#include <memory>
#include <vector>
#include <stdio.h>

#include "geodiff.h"

class GeoDiffException;

class Logger
{
  public:
    Logger();
    void setCallback( GEODIFF_LoggerCallback loggerCallback );
    void setMaxLogLevel( GEODIFF_LoggerLevel level ) { mMaxLogLevel = level; }
    GEODIFF_LoggerLevel maxLogLevel() const { return mMaxLogLevel; }
    Logger( Logger const & ) = delete;
    void operator=( Logger const & ) = delete;
    void debug( const std::string &msg ) const;
    void warn( const std::string &msg ) const;
    void error( const std::string &msg ) const;
    void info( const std::string &msg ) const;
    //! Prints error message
    void error( const GeoDiffException &exp ) const;
  private:
    GEODIFF_LoggerCallback mLoggerCallback = nullptr;
    GEODIFF_LoggerLevel mMaxLogLevel = GEODIFF_LoggerLevel::LevelError;
    void log( GEODIFF_LoggerLevel level, const std::string &msg ) const;
};

#endif // GEODIFFLOGGER_H
<file_sep>#!/bin/sh

set -eu

SCRIPT_DIR=$(dirname "$0")
case $SCRIPT_DIR in
    "/"*)
        ;;
    ".")
        SCRIPT_DIR=$(pwd)
        ;;
    *)
        SCRIPT_DIR=$(pwd)/$(dirname "$0")
        ;;
esac

LOG_FILE=/tmp/cppcheck_geodiff.txt

rm -f ${LOG_FILE}
echo "cppcheck for ${SCRIPT_DIR}/../geodiff"

cppcheck --inline-suppr \
         --template='{file}:{line},{severity},{id},{message}' \
         --enable=all --inconclusive --std=c++11 \
         -j $(nproc) \
	     -igeodiff/src/3rdparty \
         ${SCRIPT_DIR}/../geodiff/src \
         >>${LOG_FILE} 2>&1 &

PID=$!
while kill -0 $PID 2>/dev/null; do
    printf "."
    sleep 1
done
echo " done"
if ! wait $PID; then
    echo "cppcheck could not be started"
    exit 1
fi

ret_code=0

for category in "error" "style" "performance" "warning" "clarifyCalculation" "portability"; do
    if grep "${category}," ${LOG_FILE}  >/dev/null; then
        echo "ERROR: Issues in '${category}' category found:"
        grep "${category}," ${LOG_FILE}
        echo ""
        echo "${category} check failed !"
        ret_code=1
    fi
done

if [ ${ret_code} = 0 ]; then
    echo "cppcheck succeeded"
fi

exit ${ret_code}
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#include "postgresutils.h"

#include "geodiffutils.hpp"


PGresult *execSql( PGconn *c, const std::string &sql )
{
  PGresult *res = ::PQexec( c, sql.c_str() );

  if ( res && ::PQstatus( c ) == CONNECTION_OK )
  {
    int errorStatus = PQresultStatus( res );
    if ( errorStatus != PGRES_COMMAND_OK && errorStatus != PGRES_TUPLES_OK )
    {
      std::string err( PQresultErrorMessage( res ) );
      PQclear( res );
      throw GeoDiffException( "postgres cmd error: " + err + "\n\nSQL:\n" + sql );
    }

    return res;
  }
  if ( PQstatus( c ) != CONNECTION_OK )
  {
    if ( res )
      PQclear( res );
    throw GeoDiffException( "postgres conn error: " + std::string( PQerrorMessage( c ) ) );
  }
  else
  {
    throw GeoDiffException( "postgres error: out of memory" );
  }

  return nullptr;
}

std::string quotedIdentifier( const std::string &ident )
{
  std::string result = replace( ident, "\"", "\"\"" );
  return "\"" + result + "\"";
}

std::string quotedString( const std::string &value )
{
  std::string result = replace( value, "'", "''" );
  if ( result.find( '\\' ) != std::string::npos )
  {
    result = replace( result, "\\", "\\\\" );
    return "E'" + result + "'";
  }
  else
    return "'" +  result + "'";
}
<file_sep>
DROP SCHEMA IF EXISTS gd_tz_updated CASCADE;

CREATE SCHEMA gd_tz_updated;

CREATE TABLE gd_tz_updated.simple ( "fid" SERIAL PRIMARY KEY, "geometry" GEOMETRY(POINT, 4326), "created" TIMESTAMP WITHOUT TIME ZONE);

INSERT INTO gd_tz_updated.simple VALUES (1, ST_GeomFromText('Point (-1.08891928864569065 0.46101231190150482)', 4326), '2021-10-28 18:34:19.472');
INSERT INTO gd_tz_updated.simple VALUES (2, ST_GeomFromText('Point (-0.36388508891928861 0.56224350205198359)', 4326), '2021-10-28 18:34:19');
INSERT INTO gd_tz_updated.simple VALUES (3, ST_GeomFromText('Point (-0.73050615595075241 0.04240766073871405)', 4326), '2021-10-28 18:34:19.53');
<file_sep># -*- coding: utf-8 -*-
"""
    :copyright: (c) 2022 <NAME>
    :license: MIT, see LICENSE for more details.
"""

from .testutils import *
import os
import shutil
import pygeodiff


class UnitTestsPythonSingleCommit(GeoDiffTests):
    def test_skip_create(self):
        base = geodiff_test_dir() + "/" + "skip_tables" + "/" + "base.gpkg"
        modified = geodiff_test_dir() + "/" + "skip_tables" + "/" + "modified_all.gpkg"
        modified_points = (
            geodiff_test_dir() + "/" + "skip_tables" + "/" + "modified_points.gpkg"
        )
        changeset = tmpdir() + "/py" + "test_skip_create" + "/" + "changeset_points.bin"
        changeset2 = (
            tmpdir() + "/py" + "test_skip_create" + "/" + "changeset_points2.bin"
        )
        changeset_inv = (
            tmpdir() + "/py" + "test_skip_create" + "/" + "changeset_inv.bin"
        )
        patched = tmpdir() + "/py" + "test_skip_create" + "/" + "patched_points.gpkg"

        create_dir("test_skip_create")

        # ignore lines table when creating changeset
        self.geodiff.set_tables_to_skip(["lines"])

        # create changeset
        self.geodiff.create_changeset(base, modified, changeset)
        check_nchanges(self.geodiff, changeset, 4)

        # apply changeset
        shutil.copyfile(base, patched)
        self.geodiff.apply_changeset(patched, changeset)

        # check that now it is same file
        self.geodiff.create_changeset(patched, modified, changeset2)
        check_nchanges(self.geodiff, changeset2, 0)

        # check we can create inverted changeset
        os.remove(changeset2)
        self.geodiff.invert_changeset(changeset, changeset_inv)
        self.geodiff.apply_changeset(patched, changeset_inv)
        self.geodiff.create_changeset_dr(
            "sqlite", "", patched, "sqlite", "", base, changeset2
        )
        check_nchanges(self.geodiff, changeset2, 0)

        self.geodiff.set_tables_to_skip([])

    def test_skip_apply(self):
        base = geodiff_test_dir() + "/" + "skip_tables" + "/" + "base.gpkg"
        modified = geodiff_test_dir() + "/" + "skip_tables" + "/" + "modified_all.gpkg"
        modified_points = (
            geodiff_test_dir() + "/" + "skip_tables" + "/" + "modified_points.gpkg"
        )
        changeset = tmpdir() + "/py" + "test_skip_apply" + "/" + "changeset_points.bin"
        changeset2 = (
            tmpdir() + "/py" + "test_skip_apply" + "/" + "changeset_points2.bin"
        )
        changeset_inv = tmpdir() + "/py" + "test_skip_apply" + "/" + "changeset_inv.bin"
        patched = tmpdir() + "/py" + "test_skip_apply" + "/" + "patched_points.gpkg"

        create_dir("test_skip_apply")

        # create changeset
        self.geodiff.create_changeset(base, modified, changeset)
        check_nchanges(self.geodiff, changeset, 6)

        # ignore lines table when creating changeset
        self.geodiff.set_tables_to_skip(["lines"])

        # apply changeset
        shutil.copyfile(base, patched)
        self.geodiff.apply_changeset(patched, changeset)

        # check that now it is same file
        self.geodiff.create_changeset(patched, modified, changeset2)
        check_nchanges(self.geodiff, changeset2, 0)

        # check we can create inverted changeset
        os.remove(changeset2)
        self.geodiff.invert_changeset(changeset, changeset_inv)
        self.geodiff.apply_changeset(patched, changeset_inv)
        self.geodiff.create_changeset_dr(
            "sqlite", "", patched, "sqlite", "", base, changeset2
        )
        check_nchanges(self.geodiff, changeset2, 0)

        self.geodiff.set_tables_to_skip([])
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2023 <NAME>
*/

#include "gtest/gtest.h"
#include "geodiff_testutils.hpp"
#include "geodiff.h"
#include "geodiffutils.hpp"

#include <string>

TEST( CAPITest, invalid_calls )
{
  GEODIFF_ContextH invalidContext = nullptr;
  GEODIFF_ContextH context = GEODIFF_createContext();

  char buffer[200];

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_driverNameFromIndex( invalidContext, 0, buffer ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_driverIsRegistered( invalidContext, "sqlite" ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_driverIsRegistered( context, nullptr ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_CX_setLoggerCallback( invalidContext, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_CX_setMaximumLoggerLevel( invalidContext, GEODIFF_LoggerLevel::LevelWarning ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_CX_setTablesToSkip( invalidContext, 0, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_CX_setTablesToSkip( context, 1, nullptr ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_createChangesetEx( invalidContext, "sqlite", nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_createChangesetEx( context, "sqlite", nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_createChangesetEx( context, "invalid driver", " ", " ", " ", " " ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_createChangesetDr( invalidContext, "sqlite", nullptr, nullptr, "sqlite", nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_createChangesetDr( context, "sqlite", nullptr, nullptr, "sqlite", nullptr, nullptr, nullptr ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_applyChangesetEx( invalidContext, "sqlite", nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_applyChangesetEx( context, "sqlite", nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_applyChangesetEx( context, "invalid driver", " ", " ", " " ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_createRebasedChangeset( invalidContext, nullptr, nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_createRebasedChangeset( context, nullptr, nullptr, nullptr, nullptr, nullptr ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_createRebasedChangesetEx( invalidContext, "sqlite", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_createRebasedChangesetEx( context, "sqlite", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr ) );

  ASSERT_EQ( -1, GEODIFF_hasChanges( invalidContext, nullptr ) );
  ASSERT_EQ( -1, GEODIFF_hasChanges( context, nullptr ) );

  ASSERT_EQ( -1, GEODIFF_changesCount( invalidContext, nullptr ) );
  ASSERT_EQ( -1, GEODIFF_changesCount( context, nullptr ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_listChanges( invalidContext, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_listChanges( context, nullptr, nullptr ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_listChangesSummary( invalidContext, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_listChangesSummary( context, nullptr, nullptr ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_invertChangeset( invalidContext, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_invertChangeset( context, nullptr, nullptr ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_concatChanges( invalidContext, 1, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_concatChanges( context, 1, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_concatChanges( context, 2, nullptr, nullptr ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_rebase( invalidContext, nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_rebase( context, nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_rebase( context, "bad file", "bad file", "bad file", "bad file" ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_rebase( context, pathjoin( testdir(), "base.gpkg" ).c_str(), "bad file", "bad file", "bad file" ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_rebase( context, pathjoin( testdir(), "base.gpkg" ).c_str(), pathjoin( testdir(), "base.gpkg" ).c_str(), "bad file", "bad file" ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_rebaseEx( invalidContext, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_rebaseEx( context, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_makeCopy( invalidContext, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_makeCopy( context, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_makeCopy( context, "invalid driver",  " ",  " ",  " ",  " ",  " " ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_makeCopy( context, "sqlite",  " ",  " ", "invalid driver",  " ",  " " ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_makeCopySqlite( invalidContext, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_makeCopySqlite( context, nullptr, nullptr ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_dumpData( invalidContext, nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_dumpData( context, nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_dumpData( context, "invalid driver",  " ",  " ",  " " ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_schema( invalidContext, nullptr, nullptr, nullptr, nullptr ) );
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_schema( context, nullptr, nullptr, nullptr, nullptr ) );

  ASSERT_EQ( nullptr, GEODIFF_readChangeset( invalidContext, nullptr ) );
  ASSERT_EQ( nullptr, GEODIFF_readChangeset( context, nullptr ) );

  ASSERT_EQ( nullptr, GEODIFF_CR_nextEntry( invalidContext, nullptr, nullptr ) );
  bool ok;
  ASSERT_EQ( nullptr, GEODIFF_CR_nextEntry( invalidContext, nullptr, &ok ) );
  ASSERT_EQ( nullptr, GEODIFF_CR_nextEntry( context, nullptr, &ok ) );

  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_createWkbFromGpkgHeader( invalidContext, nullptr, size_t( 1 ), nullptr, nullptr ) );

  size_t size;
  const char *wkb;
  ASSERT_EQ( GEODIFF_ERROR, GEODIFF_createWkbFromGpkgHeader( invalidContext, " ", size_t( 0 ), &wkb, &size ) );

  GEODIFF_CX_destroy( context );
}

TEST( CAPITest, test_copy )
{
  GEODIFF_ContextH context = GEODIFF_createContext();
  makedir( pathjoin( tmpdir(), "test_copy" ) );

  // database "db-base.gpkg"
  // - table AUDIT has no PK
  // - custom trigger that adds entry to AUDIT table on each update of "simple" table

  {
    std::string fileOutput = pathjoin( tmpdir(), "test_copy", "db-makeCopy.gpkg" );

    ASSERT_EQ( GEODIFF_SUCCESS, GEODIFF_makeCopy(
                 context,
                 "sqlite",
                 "",
                 pathjoin( testdir(), "gpkg_custom_triggers", "db-base.gpkg" ).c_str(),
                 "sqlite",
                 "",
                 fileOutput.c_str() ) );


    // THIS DROPS ALL TRIGGERS!!
    ASSERT_FALSE( fileContentEquals( fileOutput, pathjoin( testdir(), "gpkg_custom_triggers", "db-base.gpkg" ) ) );
  }


  {
    std::string fileOutput = pathjoin( tmpdir(), "test_copy", "db-makeCopySqlite.gpkg" );

    ASSERT_EQ( GEODIFF_SUCCESS, GEODIFF_makeCopySqlite(
                 context,
                 pathjoin( testdir(), "gpkg_custom_triggers", "db-base.gpkg" ).c_str(),
                 fileOutput.c_str() ) );
  }

  GEODIFF_CX_destroy( context );
}

TEST( CAPITest, test_rebases )
{
  GEODIFF_ContextH context = GEODIFF_createContext();
  makedir( pathjoin( tmpdir(), "test_rebases" ) );

  {
    std::string fileOutput = pathjoin( tmpdir(), "test_rebases", "text_pk_A.sqlite" );
    std::string fileConflict = pathjoin( tmpdir(), "test_rebases", "output_text_pk.log" );


    ASSERT_EQ( GEODIFF_SUCCESS, GEODIFF_makeCopySqlite(
                 context,
                 pathjoin( testdir(), "sqlite_pks", "text_pk_A.sqlite" ).c_str(),
                 fileOutput.c_str() ) );


    ASSERT_EQ( GEODIFF_ERROR, GEODIFF_rebase(
                 context,
                 pathjoin( testdir(), "sqlite_pks", "text_pk.sqlite" ).c_str(),
                 pathjoin( testdir(), "sqlite_pks", "text_pk_B.sqlite" ).c_str(),
                 fileOutput.c_str(),
                 fileConflict.c_str() )
             );
  }

  {
    std::string fileOutput = pathjoin( tmpdir(), "test_rebases", "output_compose_pk.sqlite" );
    std::string fileConflict = pathjoin( tmpdir(), "test_rebases", "output_compose_pk.log" );


    ASSERT_EQ( GEODIFF_SUCCESS, GEODIFF_makeCopySqlite(
                 context,
                 pathjoin( testdir(), "sqlite_pks", "multi_primary_key_A.sqlite" ).c_str(),
                 fileOutput.c_str() ) );


    ASSERT_EQ( GEODIFF_ERROR, GEODIFF_rebase(
                 context,
                 pathjoin( testdir(), "sqlite_pks", "multi_primary_key.sqlite" ).c_str(),
                 pathjoin( testdir(), "sqlite_pks", "multi_primary_key_B.sqlite" ).c_str(),
                 fileOutput.c_str(),
                 fileConflict.c_str() )
             );
  }

  {
    std::string fileOutput = pathjoin( tmpdir(), "test_rebases", "output_custom_triggers.gpkg" );
    std::string fileConflict = pathjoin( tmpdir(), "test_rebases", "output_custom_triggers.log" );


    ASSERT_EQ( GEODIFF_SUCCESS, GEODIFF_makeCopySqlite(
                 context,
                 pathjoin( testdir(), "gpkg_custom_triggers", "db-modified_A.gpkg" ).c_str(),
                 fileOutput.c_str() ) );


    ASSERT_EQ( GEODIFF_SUCCESS, GEODIFF_rebase(
                 context,
                 pathjoin( testdir(), "gpkg_custom_triggers", "db-base.gpkg" ).c_str(),
                 pathjoin( testdir(), "gpkg_custom_triggers", "db-modified_B.gpkg" ).c_str(),
                 fileOutput.c_str(),
                 fileConflict.c_str() )
             );
  }

  GEODIFF_CX_destroy( context );
}

int main( int argc, char **argv )
{
  testing::InitGoogleTest( &argc, argv );
  init_test();
  int ret =  RUN_ALL_TESTS();
  finalize_test();
  return ret;
}
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2019 <NAME>
*/

#ifndef GEODIFFREBASE_H
#define GEODIFFREBASE_H

#include <string>
#include <vector>
#include "geodiffutils.hpp"

class Logger;

//! throws GeoDiffException on error
void rebase( const Context *context,
             const std::string &changeset_BASE_THEIRS, //in
             const std::string &changeset_THEIRS_MODIFIED, // out
             const std::string &changeset_BASE_MODIFIED, //in
             std::vector<ConflictFeature> &conflicts// out
           );


#endif // GEODIFFREBASE_H
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#include "changesetreader.h"

#include "geodiffutils.hpp"
#include "changesetgetvarint.h"
#include "portableendian.h"

#include <assert.h>
#include <memory.h>

#include <sstream>


ChangesetReader::ChangesetReader() = default;

ChangesetReader::~ChangesetReader() = default;


bool ChangesetReader::open( const std::string &filename )
{
  try
  {
    mBuffer.reset( new Buffer );
    mBuffer->read( filename );
  }
  catch ( const GeoDiffException & )
  {
    return false;
  }

  return true;
}

bool ChangesetReader::nextEntry( ChangesetEntry &entry )
{
  while ( 1 )
  {
    if ( mOffset >= mBuffer->size() )
      break;   // EOF

    int type = readByte();
    if ( type == 'T' )
    {
      readTableRecord();
      // and now continue reading, we want an entry
    }
    else if ( type == ChangesetEntry::OpInsert || type == ChangesetEntry::OpUpdate || type == ChangesetEntry::OpDelete )
    {
      readByte();
      if ( type != ChangesetEntry::OpInsert )
        readRowValues( entry.oldValues );
      else
        entry.oldValues.erase( entry.oldValues.begin(), entry.oldValues.end() );
      if ( type != ChangesetEntry::OpDelete )
        readRowValues( entry.newValues );
      else
        entry.newValues.erase( entry.newValues.begin(), entry.newValues.end() );

      entry.op = static_cast<ChangesetEntry::OperationType>( type );
      entry.table = &mCurrentTable;
      return true;  // we're done!
    }
    else
    {
      throwReaderError( "Unknown entry type " + std::to_string( type ) );
    }
  }
  return false;
}

bool ChangesetReader::isEmpty() const
{
  return mBuffer->size() == 0;
}

void ChangesetReader::rewind()
{
  mOffset = 0;
  mCurrentTable = ChangesetTable();
}

char ChangesetReader::readByte()
{
  if ( mOffset >= mBuffer->size() )
    throwReaderError( "readByte: at the end of buffer" );
  const char *ptr = mBuffer->c_buf() + mOffset;
  ++mOffset;
  return *ptr;
}

int ChangesetReader::readVarint()
{
  u32 value;
  const unsigned char *ptr = ( const unsigned char * )mBuffer->c_buf() + mOffset;
  int nBytes = getVarint32( ptr, value );
  mOffset += nBytes;
  return value;
}

std::string ChangesetReader::readNullTerminatedString()
{
  const char *ptr = mBuffer->c_buf() + mOffset;
  int count = 0;
  while ( mOffset + count < mBuffer->size() && ptr[count] )
    ++count;

  if ( mOffset + count >= mBuffer->size() )
    throwReaderError( "readNullTerminatedString: at the end of buffer" );

  mOffset += count + 1;
  return std::string( ptr, count );
}

void ChangesetReader::readRowValues( std::vector<Value> &values )
{
  // let's ensure we have the right size of array
  if ( values.size() != mCurrentTable.columnCount() )
  {
    values.resize( mCurrentTable.columnCount() );
  }

  for ( size_t i = 0; i < mCurrentTable.columnCount(); ++i )
  {
    int type = readByte();
    if ( type == Value::TypeInt ) // 0x01
    {
      // 64-bit int (big endian)
      int64_t v;
      uint64_t x;
      memcpy( &x, mBuffer->c_buf() + mOffset, 8 );
      mOffset += 8;
      x = be64toh( x ); // convert big endian to host
      memcpy( &v, &x, 8 );
      values[i].setInt( v );
    }
    else if ( type == Value::TypeDouble ) // 0x02
    {
      // 64-bit double (big endian)
      double v;
      uint64_t x;
      memcpy( &x, mBuffer->c_buf() + mOffset, 8 );
      mOffset += 8;
      x = be64toh( x ); // convert big endian to host
      memcpy( &v, &x, 8 );
      values[i].setDouble( v );
    }
    else if ( type == Value::TypeText || type == Value::TypeBlob ) // 0x03 or 0x04
    {
      int len = readVarint();
      if ( mOffset + len > mBuffer->size() )
        throwReaderError( "readRowValues: text/blob: at the end of buffer" );
      values[i].setString( type == Value::TypeText ? Value::TypeText : Value::TypeBlob, mBuffer->c_buf() + mOffset, len );
      mOffset += len;
    }
    else if ( type == Value::TypeNull ) // 0x05
    {
      values[i].setNull();
    }
    else if ( type == Value::TypeUndefined )  // undefined value  (different from NULL)
    {
      values[i].setUndefined();
    }
    else
    {
      throwReaderError( "readRowValues: unexpected entry type" );
    }
  }
}

void ChangesetReader::readTableRecord()
{
  /* A 'table' record consists of:
  **
  **   * A constant 'T' character,
  **   * Number of columns in said table (a varint),
  **   * An array of nCol bytes (sPK),
  **   * A nul-terminated table name.
  */

  int nCol = readVarint();
  if ( nCol < 0 || nCol > 65536 )
    throwReaderError( "readByte: unexpected number of columns" );

  mCurrentTable.primaryKeys.clear();

  for ( int i = 0; i < nCol; ++i )
  {
    mCurrentTable.primaryKeys.push_back( readByte() );
  }

  mCurrentTable.name = readNullTerminatedString();
}


void ChangesetReader::throwReaderError( const std::string &message ) const
{
  std::ostringstream stringStream;
  stringStream << "Reader error at offset " << mOffset << ":\n" << message;
  std::string str = stringStream.str();
  throw GeoDiffException( str );
}
<file_sep>
# Changeset Format

The format for changesets is borrowed from SQLite3 session extension's internal format
and it is currently 100% compatible with it. Below are details of the format, extracted
from SQLite3 source code.

## Summary

A changeset is a collection of DELETE, UPDATE and INSERT operations on
one or more tables. Operations on a single table are grouped together,
but may occur in any order (i.e. deletes, updates and inserts are all
mixed together).

Each group of changes begins with a table header:

- 1 byte: Constant 0x54 (capital 'T')
- Varint: Number of columns in the table.
- nCol bytes: 0x01 for PK columns, 0x00 otherwise.
- N bytes: Unqualified table name (encoded using UTF-8). Nul-terminated.

Followed by one or more changes to the table.

- 1 byte: Either SQLITE_INSERT (0x12), UPDATE (0x17) or DELETE (0x09).
- 1 byte: The "indirect-change" flag.
- old.* record: (delete and update only)
- new.* record: (insert and update only)

The "old.*" and "new.*" records, if present, are N field records in the
format described above under "RECORD FORMAT", where N is the number of
columns in the table. The i'th field of each record is associated with
the i'th column of the table, counting from left to right in the order
in which columns were declared in the CREATE TABLE statement.

The new.* record that is part of each INSERT change contains the values
that make up the new row. Similarly, the old.* record that is part of each
DELETE change contains the values that made up the row that was deleted
from the database. In the changeset format, the records that are part
of INSERT or DELETE changes never contain any undefined (type byte 0x00)
fields.

Within the old.* record associated with an UPDATE change, all fields
associated with table columns that are not PRIMARY KEY columns and are
not modified by the UPDATE change are set to "undefined". Other fields
are set to the values that made up the row before the UPDATE that the
change records took place. Within the new.* record, fields associated
with table columns modified by the UPDATE change contain the new
values. Fields associated with table columns that are not modified
are set to "undefined".

# Record Format

Unlike the SQLite database record format, each field is self-contained -
there is no separation of header and data. Each field begins with a
single byte describing its type, as follows:

       0x00: Undefined value.
       0x01: Integer value.
       0x02: Real value.
       0x03: Text value.
       0x04: Blob value.
       0x05: SQL NULL value.

Note that the above match the definitions of SQLITE_INTEGER, SQLITE_TEXT
and so on in sqlite3.h. For undefined and NULL values, the field consists
only of the single type byte. For other types of values, the type byte
is followed by:

- Text values:
  A varint containing the number of bytes in the value (encoded using
  UTF-8). Followed by a buffer containing the UTF-8 representation
  of the text value. There is no nul terminator.

- Blob values:
  A varint containing the number of bytes in the value, followed by
  a buffer containing the value itself.

- Integer values:
  An 8-byte big-endian integer value.

- Real values:
  An 8-byte big-endian IEEE 754-2008 real value.


# Varint Format

Varint values are encoded in the same way as varints in the SQLite
record format.

The variable-length integer encoding is as follows:

```
 KEY:
         A = 0xxxxxxx    7 bits of data and one flag bit
         B = 1xxxxxxx    7 bits of data and one flag bit
         C = xxxxxxxx    8 bits of data

  7 bits - A
 14 bits - BA
 21 bits - BBA
 28 bits - BBBA
 35 bits - BBBBA
 42 bits - BBBBBA
 49 bits - BBBBBBA
 56 bits - BBBBBBBA
 64 bits - BBBBBBBBC
```
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#include "changesetwriter.h"

#include "geodiffutils.hpp"
#include "changesetputvarint.h"
#include "portableendian.h"

#include <assert.h>
#include <memory.h>

#include <sstream>

void ChangesetWriter::open( const std::string &filename )
{
#ifdef WIN32
  mFile.open( stringToWString( filename ), std::ios::out | std::ios::binary );
#else
  mFile.open( filename, std::ios::out | std::ios::binary );
#endif
  if ( !mFile.is_open() )
    throw GeoDiffException( "Unable to open changeset file for writing: " + filename );
}

void ChangesetWriter::beginTable( const ChangesetTable &table )
{
  mCurrentTable = table;

  writeByte( 'T' );
  writeVarint( ( int ) table.columnCount() );
  for ( size_t i = 0; i < table.columnCount(); ++i )
    writeByte( table.primaryKeys[i] );
  writeNullTerminatedString( table.name );
}

void ChangesetWriter::writeEntry( const ChangesetEntry &entry )
{
  if ( entry.op != ChangesetEntry::OpInsert && entry.op != ChangesetEntry::OpUpdate && entry.op != ChangesetEntry::OpDelete )
    throw GeoDiffException( "wrong op for changeset entry" );
  writeByte( ( char ) entry.op );
  writeByte( 0 );  // "indirect" always false

  if ( entry.op != ChangesetEntry::OpInsert )
    writeRowValues( entry.oldValues );
  if ( entry.op != ChangesetEntry::OpDelete )
    writeRowValues( entry.newValues );
}

void ChangesetWriter::writeByte( char c )
{
  mFile.write( &c, 1 );
}

void ChangesetWriter::writeVarint( int n )
{
  unsigned char output[9];  // 1-9 bytes
  int numBytes = putVarint32( output, n );
  mFile.write( ( char * )output, numBytes );
}

void ChangesetWriter::writeNullTerminatedString( const std::string &str )
{
  mFile.write( str.c_str(), str.size() + 1 );
}

void ChangesetWriter::writeRowValues( const std::vector<Value> &values )
{
  if ( values.size() != mCurrentTable.columnCount() )
    throw GeoDiffException( "wrong number of rows in the entry" );

  for ( size_t i = 0; i < mCurrentTable.columnCount(); ++i )
  {
    Value::Type type = values[i].type();
    writeByte( ( char ) type );
    if ( type == Value::TypeInt ) // 0x01
    {
      // 64-bit int (big endian)
      uint64_t x;
      int64_t v = values[i].getInt();
      memcpy( &x, &v, 8 );
      x = htobe64( x ); // convert host to big endian
      mFile.write( ( char * )&x, 8 );
    }
    else if ( type == Value::TypeDouble ) // 0x02
    {
      // 64-bit double (big endian)
      int64_t x;
      double v = values[i].getDouble();
      memcpy( &x, &v, 8 );
      x = htobe64( x ); // convert host to big endian
      mFile.write( ( char * )&x, 8 );
    }
    else if ( type == Value::TypeText || type == Value::TypeBlob ) // 0x03 or 0x04
    {
      const std::string &str = values[i].getString();
      writeVarint( ( int ) str.size() );
      mFile.write( str.c_str(), str.size() );
    }
    else if ( type == Value::TypeNull ) // 0x05
    {
      // nothing extra to write
    }
    else if ( type == Value::TypeUndefined )  // undefined value  (different from NULL)
    {
      // nothing extra to write
    }
    else
    {
      throw GeoDiffException( "unexpected entry type" );
    }
  }
}
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2019 <NAME>
*/

#include "gtest/gtest.h"
#include "geodiff_testutils.hpp"
#include "geodiff.h"
#include "geodiffutils.hpp"

TEST( ModifiedSchemeSqlite3Test, add_attribute )
{
  std::cout << "geopackage add attribute to table" << std::endl;
  std::string testname = "added_attribute";
  makedir( pathjoin( tmpdir(), testname ) );

  std::string base = pathjoin( testdir(), "base.gpkg" );
  std::string modified = pathjoin( testdir(), "modified_scheme", "added_attribute.gpkg" );
  std::string changeset = pathjoin( tmpdir(), testname, "changeset.bin" );

  ASSERT_EQ( GEODIFF_createChangeset( testContext(), base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_ERROR );
}

TEST( ModifiedSchemeSqlite3Test, add_table )
{
  std::cout << "geopackage add table to table" << std::endl;
  std::string testname = "add_table";
  makedir( pathjoin( tmpdir(), testname ) );

  std::string base = pathjoin( testdir(), "base.gpkg" );
  std::string modified = pathjoin( testdir(), "modified_scheme", "added_table.gpkg" );
  std::string changeset = pathjoin( tmpdir(), testname, "changeset.bin" );

  ASSERT_EQ( GEODIFF_createChangeset( testContext(), base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_ERROR );
}

TEST( ModifiedSchemeSqlite3Test, delete_attribute )
{
  std::cout << "geopackage add attribute to table" << std::endl;
  std::string testname = "delete_attribute";
  makedir( pathjoin( tmpdir(), testname ) );

  std::string base = pathjoin( testdir(), "modified_scheme", "added_attribute.gpkg" );
  std::string modified = pathjoin( testdir(), "base.gpkg" );
  std::string changeset = pathjoin( tmpdir(), testname, "changeset.bin" );

  ASSERT_EQ( GEODIFF_createChangeset( testContext(), base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_ERROR );
}

TEST( ModifiedSchemeSqlite3Test, delete_table )
{
  std::cout << "geopackage delete table" << std::endl;
  std::string testname = "delete_table";
  makedir( pathjoin( tmpdir(), testname ) );

  std::string base = pathjoin( testdir(), "modified_scheme", "added_table.gpkg" );
  std::string modified = pathjoin( testdir(), "base.gpkg" );
  std::string changeset = pathjoin( tmpdir(), testname, "changeset.bin" );

  ASSERT_EQ( GEODIFF_createChangeset( testContext(), base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_ERROR );
}

TEST( ModifiedSchemeSqlite3Test, rename_table )
{
  std::cout << "geopackage table count is same, but tables have different name" << std::endl;
  std::string testname = "delete_table";
  makedir( pathjoin( tmpdir(), testname ) );

  std::string base = pathjoin( testdir(), "modified_scheme", "added_table.gpkg" );
  std::string modified = pathjoin( testdir(), "modified_scheme", "added_table2.gpkg" );
  std::string changeset = pathjoin( tmpdir(), testname, "changeset.bin" );

  ASSERT_EQ( GEODIFF_createChangeset( testContext(), base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_ERROR );
}

TEST( ModifiedSchemeSqlite3Test, rename_attribute )
{
  std::cout << "geopackage attribute count is same, but have different name" << std::endl;
  std::string testname = "rename_attribute";
  makedir( pathjoin( tmpdir(), testname ) );

  std::string base = pathjoin( testdir(), "modified_scheme", "added_attribute.gpkg" );
  std::string modified = pathjoin( testdir(), "modified_scheme", "added_attribute2.gpkg" );
  std::string changeset = pathjoin( tmpdir(), testname, "changeset.bin" );

  ASSERT_EQ( GEODIFF_createChangeset( testContext(), base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_ERROR );
}

int main( int argc, char **argv )
{
  testing::InitGoogleTest( &argc, argv );
  init_test();
  int ret =  RUN_ALL_TESTS();
  finalize_test();
  return ret;
}
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#include "changesetutils.h"

#include "base64utils.h"
#include "geodiffutils.hpp"
#include "changesetreader.h"
#include "changesetwriter.h"
#include "tableschema.h"


ChangesetTable schemaToChangesetTable( const std::string &tableName, const TableSchema &tbl )
{
  ChangesetTable chTable;
  chTable.name = tableName;
  for ( const TableColumnInfo &c : tbl.columns )
    chTable.primaryKeys.push_back( c.isPrimaryKey );
  return chTable;
}

void invertChangeset( ChangesetReader &reader, ChangesetWriter &writer )
{
  std::string currentTableName;
  std::vector<bool> currentPkeys;
  ChangesetEntry entry;
  while ( reader.nextEntry( entry ) )
  {
    assert( entry.table );
    if ( entry.table->name != currentTableName )
    {
      writer.beginTable( *entry.table );
      currentTableName = entry.table->name;
      currentPkeys = entry.table->primaryKeys;
    }

    if ( entry.op == ChangesetEntry::OpInsert )
    {
      ChangesetEntry out;
      out.op = ChangesetEntry::OpDelete;
      out.oldValues = entry.newValues;
      writer.writeEntry( out );
    }
    else if ( entry.op == ChangesetEntry::OpDelete )
    {
      ChangesetEntry out;
      out.op = ChangesetEntry::OpInsert;
      out.newValues = entry.oldValues;
      writer.writeEntry( out );
    }
    else if ( entry.op == ChangesetEntry::OpUpdate )
    {
      ChangesetEntry out;
      out.op = ChangesetEntry::OpUpdate;
      out.newValues = entry.oldValues;
      out.oldValues = entry.newValues;
      // if a column is a part of pkey and has not been changed,
      // the original entry has "old" value the pkey value and "new"
      // value is undefined - let's reverse "old" and "new" in that case.
      for ( size_t i = 0; i < currentPkeys.size(); ++i )
      {
        if ( currentPkeys[i] && out.oldValues[i].type() == Value::TypeUndefined )
        {
          out.oldValues[i] = out.newValues[i];
          out.newValues[i].setUndefined();
        }
      }
      writer.writeEntry( out );
    }
    else
    {
      throw GeoDiffException( "Unknown entry operation!" );
    }
  }
}

nlohmann::json valueToJSON( const Value &value )
{
  nlohmann::json j;
  switch ( value.type() )
  {
    case Value::TypeUndefined:
      break;  // actually this not get printed - undefined value should be omitted completely
    case Value::TypeInt:
      j = value.getInt();
      break;
    case Value::TypeDouble:
      j = value.getDouble();
      break;
    case Value::TypeText:
      j = value.getString();
      break;
    case Value::TypeBlob:
    {
      // this used to either show "blob N bytes" or would be converted to WKT
      // but this is better - it preserves content of any type + can be decoded back
      std::string base64 = base64_encode( ( const unsigned char * ) value.getString().data(), ( unsigned int ) value.getString().size() );
      j = base64;
      break;
    }
    case Value::TypeNull:
      j = "null";
      break;
    default:
      j = "(unknown)";  // should never happen
  }
  return j;
}


nlohmann::json changesetEntryToJSON( const ChangesetEntry &entry )
{
  std::string status;
  if ( entry.op == ChangesetEntry::OpUpdate )
    status = "update";
  else if ( entry.op == ChangesetEntry::OpInsert )
    status = "insert";
  else if ( entry.op == ChangesetEntry::OpDelete )
    status = "delete";

  nlohmann::json res;
  res[ "table" ] = entry.table->name;
  res[ "type" ] = status;

  auto entries = nlohmann::json::array();

  Value valueOld, valueNew;
  for ( size_t i = 0; i < entry.table->columnCount(); ++i )
  {
    valueNew = ( entry.op == ChangesetEntry::OpUpdate || entry.op == ChangesetEntry::OpInsert ) ? entry.newValues[i] : Value();
    valueOld = ( entry.op == ChangesetEntry::OpUpdate || entry.op == ChangesetEntry::OpDelete ) ? entry.oldValues[i] : Value();

    nlohmann::json change;

    if ( valueNew.type() != Value::TypeUndefined || valueOld.type() != Value::TypeUndefined )
    {
      change[ "column" ] = i;

      nlohmann::json jsonValueOld = valueToJSON( valueOld );
      nlohmann::json jsonValueNew = valueToJSON( valueNew );

      if ( !jsonValueOld.empty() )
      {
        if ( jsonValueOld == "null" )
          change[ "old" ] = nullptr;
        else
          change[ "old" ] = jsonValueOld;
      }
      if ( !jsonValueNew.empty() )
      {
        if ( jsonValueNew == "null" )
          change[ "new" ] = nullptr;
        else
          change[ "new" ] = jsonValueNew;
      }

      entries.push_back( change );
    }
  }

  res[ "changes" ] = entries;
  return res;
}

nlohmann::json changesetToJSON( ChangesetReader &reader )
{
  auto entries = nlohmann::json::array();

  ChangesetEntry entry;
  while ( reader.nextEntry( entry ) )
  {
    nlohmann::json msg = changesetEntryToJSON( entry );
    if ( msg.empty() )
      continue;

    entries.push_back( msg );
  }

  nlohmann::json res;
  res[ "geodiff" ] = entries;
  return res;
}

//! auxiliary table used to create table changes summary
struct TableSummary
{
  TableSummary() : inserts( 0 ), updates( 0 ), deletes( 0 ) {}
  int inserts;
  int updates;
  int deletes;
};

nlohmann::json changesetToJSONSummary( ChangesetReader &reader )
{
  std::map< std::string, TableSummary > summary;

  ChangesetEntry entry;
  while ( reader.nextEntry( entry ) )
  {
    std::string tableName = entry.table->name;
    TableSummary &tableSummary = summary[tableName];

    if ( entry.op == ChangesetEntry::OpUpdate )
      ++tableSummary.updates;
    else if ( entry.op == ChangesetEntry::OpInsert )
      ++tableSummary.inserts;
    else if ( entry.op == ChangesetEntry::OpDelete )
      ++tableSummary.deletes;
  }

  // write JSON
  auto entries = nlohmann::json::array();
  for ( const auto &kv : summary )
  {
    nlohmann::json tableJson;
    tableJson[ "table" ] = kv.first;
    tableJson[ "insert" ] = kv.second.inserts;
    tableJson[ "update" ] = kv.second.updates;
    tableJson[ "delete" ] = kv.second.deletes;

    entries.push_back( tableJson );
  }
  nlohmann::json res;
  res[ "geodiff_summary" ] = entries;
  return res;
}

nlohmann::json conflictToJSON( const ConflictFeature &conflict )
{
  nlohmann::json res;
  res[ "table" ] = std::string( conflict.tableName() );
  res[ "type" ] = "conflict";
  res[ "fid" ] = std::to_string( conflict.pk() );

  auto entries = nlohmann::json::array();

  const std::vector<ConflictItem> items = conflict.items();
  for ( const ConflictItem &item : items )
  {
    nlohmann::json change;
    change[ "column" ] = item.column();

    nlohmann::json valueBase = valueToJSON( item.base() );
    nlohmann::json valueOld = valueToJSON( item.theirs() );
    nlohmann::json valueNew = valueToJSON( item.ours() );

    if ( !valueBase.empty() )
    {
      if ( valueBase == "null" )
        change[ "base" ] = nullptr;
      else
        change[ "base" ] = valueBase;
    }
    if ( !valueOld.empty() )
    {
      if ( valueOld == "null" )
        change[ "old" ] = nullptr;
      else
        change[ "old" ] = valueOld;
    }
    if ( !valueNew.empty() )
    {
      if ( valueNew == "null" )
        change[ "new" ] = nullptr;
      else
        change[ "new" ] = valueNew;
    }

    entries.push_back( change );
  }
  res[ "changes" ] = entries;
  return res;
}

nlohmann::json conflictsToJSON( const std::vector<ConflictFeature> &conflicts )
{
  auto entries = nlohmann::json::array();
  for ( const ConflictFeature &item : conflicts )
  {
    nlohmann::json msg = conflictToJSON( item );
    if ( msg.empty() )
      continue;

    entries.push_back( msg );
  }

  nlohmann::json res;
  res[ "geodiff" ] = entries;
  return res;
}

inline int hex2num( unsigned char i )
{
  if ( i <= '9' && i >= '0' )
    return i - '0';
  if ( i >= 'A' && i <= 'F' )
    return 10 + i - 'A';
  if ( i >= 'a' && i <= 'f' )
    return 10 + i - 'a';
  assert( false );
  return 0; // should never happen
}

inline char num2hex( int n )
{
  assert( n >= 0 && n < 16 );
  if ( n >= 0 && n < 10 )
    return char( '0' + n );
  else if ( n >= 10 && n < 16 )
    return char( 'A' + n - 10 );
  return '?';  // should never happen
}

std::string hex2bin( const std::string &str )
{
  assert( str.size() % 2 == 0 );
  std::string output( str.size() / 2, 0 );
  for ( size_t i = 0; i < str.size(); i += 2 )
  {
    int n1 = hex2num( str[i] ), n2 = hex2num( str[i + 1] );
    output[i / 2] = char( n1 * 16 + n2 );
  }
  return output;
}

std::string bin2hex( const std::string &str )
{
  std::string output( str.size() * 2, 0 );
  for ( size_t i = 0; i < str.size(); ++i )
  {
    unsigned char ch = str[i];
    output[i * 2] = num2hex( ch / 16 );
    output[i * 2 + 1] = num2hex( ch % 16 );
  }
  return output;
}
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef DRIVER_H
#define DRIVER_H

#include <map>
#include <memory>
#include <vector>
#include <string>

#include "geodiff.h"
#include "tableschema.h"

class Context;
class ChangesetReader;
class ChangesetWriter;

typedef std::map<std::string, std::string> DriverParametersMap;


/**
 * Abstracts all backend-specific work.
 *
 * A driver is normally opened with a reference to two data sources - the "base" ("old") source and
 * the "modified" ("new") data source. By comparing the two sources, it can create changesets
 * using createChangeset() method.
 *
 * When applying an existing changeset using applyChangeset() method, we only need one source which
 * will be modified. In this case, a driver may be opened with a single source only, but it will
 * not be possible to call createChangeset() because of missing second data source.
 *
 * Supported driver names:
 *
 * - "sqlite" - compares two sqlite database files. GeoPackages are supported as well.
 *    Use sqliteParameters() or sqliteParametersSingleSource() to get parameters to open the driver.
 * - "postgres" - TODO:add docs
 *
 * Use createDriver() to create instance of a driver.
 */
class Driver
{
  public:

    /**
     * Returns list of supported driver names
     */
    static std::vector<std::string> drivers();

    /**
     * Returns whether driver is supported
     */
    static bool driverIsRegistered( const std::string &driverName );

    /**
     * Returns a new instance of a driver given its name. Returns nullptr if such driver does not exist.
     */
    static std::unique_ptr<Driver> createDriver( const Context *context, const std::string &driverName );

    /**
     * Returns driver parameters for Sqlite driver - it needs filenames of two sqlite databases.
     */
    static DriverParametersMap sqliteParameters( const std::string &filenameBase, const std::string &filenameModified );

    /**
     * Returns driver parameters for Sqlite driver, but only using a single database.
     */
    static DriverParametersMap sqliteParametersSingleSource( const std::string &filename );

    //

    explicit Driver( const Context *context );
    virtual ~Driver();

    /**
     * Opens a geodiff session using a set of key-value pairs with connection configuration.
     * The expected keys and values depend on the driver being used.
     *
     * On error the function throws GeoDiffException with the cause.
     */
    virtual void open( const DriverParametersMap &conn ) = 0;

    /**
     * Opens a new geodiff session that creates data source. For example, for Sqlite this means creating
     * a new database file, for Postgres this is creation of the specified database schema (namespace).
     * \note This method only uses 'base' database ('modified' does not need to be specified)
     */
    virtual void create( const DriverParametersMap &conn, bool overwrite = false ) = 0;

    /**
     * Returns a list of tables in the current connection. The useModified argument
     * decides whether the list should be created for the base file/schema or for the locally
     * modified file/schema.
     */
    virtual std::vector<std::string> listTables( bool useModified = false ) = 0;

    /**
     * Returns table schema information for a given table. This is used to check compatibility
     * between different tables.
     */
    virtual TableSchema tableSchema( const std::string &tableName, bool useModified = false ) = 0;

    /**
     * Writes changes between base and modified tables to the given writer
     * \note This method requires that both 'base' and 'modified' databases have been specified
     *       when opening the driver.
     */
    virtual void createChangeset( ChangesetWriter &writer ) = 0;

    /**
     * Reads changes from the given reader and tries to apply them to the tables.
     * \note This method only uses 'base' database ('modified' does not need to be specified when opening)
     */
    virtual void applyChangeset( ChangesetReader &reader ) = 0;

    /**
     * Creates empty tables based on the definition given by 'tables' argument.
     * \note This method only uses 'base' database ('modified' does not need to be specified when opening)
     */
    virtual void createTables( const std::vector<TableSchema> &tables ) = 0;

    /**
     * Writes all rows of the specified table to a changeset (it will output only INSERT operations)
     */
    virtual void dumpData( ChangesetWriter &writer, bool useModified = false ) = 0;

    /**
     * Tests whether the table schemas are compatible with our rebase algorithm, i.e. no unsupported
     * database features are used. Currently, for example, geodiff rebase does not deal with foreign
     * keys or with user-defined triggers.
     *
     * If the check fails, GeoDiffException is thrown.
     */
    virtual void checkCompatibleForRebase( bool useModified = false ) = 0;

    static const std::string SQLITEDRIVERNAME;
    static const std::string POSTGRESDRIVERNAME;

    const Context *context() const;

  private:
    const Context *mContext; // never null
};


#endif // DRIVER_H
<file_sep>import os
import shutil
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--version', help='version to fetch')
parser.add_argument('--python_version', nargs='?', default=37, help='python version to fetch')
args = parser.parse_args()
VERSION = args.version
PYTHON_VERSION = str(args.python_version)
print("using version " + VERSION)
print("python version " + PYTHON_VERSION)

THIS_DIR = os.path.dirname(os.path.realpath(__file__))
RESULT_DIR = os.path.join(THIS_DIR, os.pardir, "build-platforms")
TMPDIR = os.path.join(RESULT_DIR, "tmp")
FINALDIR = os.path.join(RESULT_DIR, "pygeodiff")
PWD = os.curdir

if os.path.exists(RESULT_DIR):
    shutil.rmtree(RESULT_DIR)
os.makedirs(RESULT_DIR)
os.makedirs(TMPDIR)

source = "pygeodiff-" + VERSION + ".tar.gz"
plats = ["win32",
         "win_amd64",
         "macosx_10_9_x86_64",
         "manylinux_2_24_x86_64"
         ]

print("Download")
os.chdir(TMPDIR)
for plat in plats:
    print("Fetching " + plat)
    os.system("pip3 download --only-binary=:all: --no-deps --platform "+plat+" --python-version "+PYTHON_VERSION+" --implementation cp --abi cp"+PYTHON_VERSION+"m pygeodiff==" + VERSION )

print("Extract & Combine")
for plat in plats:
    platdir = "pygeodiff-" + VERSION + "-" + plat
    os.system("unzip pygeodiff-" + VERSION + "-cp"+PYTHON_VERSION+"-cp"+PYTHON_VERSION+"m-" + plat + ".whl -d " + platdir )
    if not os.path.exists(FINALDIR):
        os.mkdir(FINALDIR)
    os.system("cp "+platdir+"/pygeodiff/* "+FINALDIR+"/")

if ((not os.path.exists(FINALDIR)) or
    (not os.path.exists(FINALDIR + "/pygeodiff-" + VERSION + "-python.pyd")) or
    (not os.path.exists(FINALDIR + "/pygeodiff-" + VERSION + "-python-win32.pyd")) or
    (not os.path.exists(FINALDIR + "/libpygeodiff-" + VERSION + "-python.dylib")) or
    (not os.path.exists(FINALDIR + "/libpygeodiff-" + VERSION + "-python.so"))
   ):
    print ("ERROR")
    exit(1)
else:
    print("Done")
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef POSTGRESUTILS_H
#define POSTGRESUTILS_H

#include <assert.h>
#include <string>

extern "C"
{
#include <libpq-fe.h>
}


class PostgresResult
{
  public:
    explicit PostgresResult( PGresult *result ) : mResult( result ) {}
    ~PostgresResult()
    {
      if ( mResult )
        ::PQclear( mResult );
      mResult = nullptr;
    }

    ExecStatusType status() const
    {
      return mResult ? ::PQresultStatus( mResult ) : PGRES_FATAL_ERROR;
    }

    std::string statusErrorMessage() const
    {
      assert( mResult );
      return ::PQresultErrorMessage( mResult );
    }

    int rowCount() const
    {
      assert( mResult );
      return ::PQntuples( mResult );
    }

    std::string affectedRows() const
    {
      assert( mResult );
      return ::PQcmdTuples( mResult );
    }

    std::string value( int row, int col ) const
    {
      assert( mResult );
      return isNull( row, col )
             ? std::string()
             : std::string( ::PQgetvalue( mResult, row, col ) );
    }

    bool isNull( int row, int col ) const
    {
      assert( mResult );
      return ::PQgetisnull( mResult, row, col );
    }

  private:
    PGresult *mResult = nullptr;

};

PGresult *execSql( PGconn *c, const std::string &sql );

std::string quotedIdentifier( const std::string &ident );
std::string quotedString( const std::string &value );


#endif // POSTGRESUTILS_H
<file_sep>#!/bin/bash

SCRIPT_DIR=$(dirname "$0")
case $SCRIPT_DIR in
    "/"*)
        ;;
    ".")
        SCRIPT_DIR=$(pwd)
        ;;
    *)
        SCRIPT_DIR=$(pwd)/$(dirname "$0")
        ;;
esac

BLACK=$(which black)
if [ $? -ne 0 ]; then
	echo "[!] black not installed." >&2
	exit 1
fi
$BLACK --version

echo "running run_black for $SCRIPT_DIR/../pygeodiff"

$BLACK $SCRIPT_DIR/../pygeodiff --check --quiet
RETURN=$?

# this does not return 0 or 1 exit code
$BLACK $SCRIPT_DIR/../pygeodiff --verbose 

exit $RETURN
<file_sep>
DROP SCHEMA IF EXISTS gd_inserted_1_a CASCADE;

CREATE SCHEMA gd_inserted_1_a;

CREATE TABLE gd_inserted_1_a.simple ( "fid" SERIAL PRIMARY KEY, "geometry" GEOMETRY(POINT, 4326), "name" TEXT, "rating" INTEGER);

INSERT INTO gd_inserted_1_a.simple VALUES (1, ST_GeomFromText('Point (-1.08891928864569065 0.46101231190150482)', 4326), 'feature1', 1);
INSERT INTO gd_inserted_1_a.simple VALUES (2, ST_GeomFromText('Point (-0.36388508891928861 0.56224350205198359)', 4326), 'feature2', 2);
INSERT INTO gd_inserted_1_a.simple VALUES (3, ST_GeomFromText('Point (-0.73050615595075241 0.04240766073871405)', 4326), 'feature3', 3);
INSERT INTO gd_inserted_1_a.simple VALUES (4, ST_GeomFromText('Point (-0.80989507554245277 0.35087659877358479)', 4326), 'my new point A', 1);
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef CHANGESETPUTVARINT_H
#define CHANGESETPUTVARINT_H

#include <stdint.h>

// Contents of this file is entirely based on code from sqlite3
//
// the following macro should be used for varint writing:
// - putVarint32


typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;


#define putVarint32(A,B)  \
  (u8)(((u32)(B)<(u32)0x80)?(*(A)=(unsigned char)(B)),1:\
       sqlite3PutVarint((A),(B)))

/*
** Write a 64-bit variable-length integer to memory starting at p[0].
** The length of data write will be between 1 and 9 bytes.  The number
** of bytes written is returned.
**
** A variable-length integer consists of the lower 7 bits of each byte
** for all bytes that have the 8th bit set and one byte with the 8th
** bit clear.  Except, if we get to the 9th byte, it stores the full
** 8 bits and is the last byte.
*/
static int putVarint64( unsigned char *p, u64 v )
{
  int i, j, n;
  u8 buf[10];
  if ( v & ( ( ( u64 )0xff000000 ) << 32 ) )
  {
    p[8] = ( u8 )v;
    v >>= 8;
    for ( i = 7; i >= 0; i-- )
    {
      p[i] = ( u8 )( ( v & 0x7f ) | 0x80 );
      v >>= 7;
    }
    return 9;
  }
  n = 0;
  do
  {
    buf[n++] = ( u8 )( ( v & 0x7f ) | 0x80 );
    v >>= 7;
  }
  while ( v != 0 );
  buf[0] &= 0x7f;
  assert( n <= 9 );
  for ( i = 0, j = n - 1; j >= 0; j--, i++ )
  {
    p[i] = buf[j];
  }
  return n;
}

static int sqlite3PutVarint( unsigned char *p, u64 v )
{
  if ( v <= 0x7f )
  {
    p[0] = v & 0x7f;
    return 1;
  }
  if ( v <= 0x3fff )
  {
    p[0] = ( ( v >> 7 ) & 0x7f ) | 0x80;
    p[1] = v & 0x7f;
    return 2;
  }
  return putVarint64( p, v );
}

#endif // CHANGESETPUTVARINT_H
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef CHANGESETREADER_H
#define CHANGESETREADER_H


#include "geodiff.h"

#include "changeset.h"


class Buffer;

/**
 * Class for reading of binary changeset files.
 * First use open() to initialize it, followed by a series of nextEntry() calls.
 *
 * See changeset-format.md for the documentation of the format.
 */
class ChangesetReader
{
  public:
    ChangesetReader();
    ~ChangesetReader();

    //! Starts reading of changeset from a file
    bool open( const std::string &filename );

    //! Reads next changeset entry to the passed object
    bool nextEntry( ChangesetEntry &entry );

    //! Returns whether the changeset being read is completely empty
    bool isEmpty() const;

    //! Resets the reader position back to the start of the changeset
    void rewind();

  private:

    char readByte();
    int readVarint();
    std::string readNullTerminatedString();
    void readRowValues( std::vector<Value> &values );
    void readTableRecord();

    void throwReaderError( const std::string &message ) const;

    int mOffset = 0;  // where are we in the buffer

    std::unique_ptr<Buffer> mBuffer;

    ChangesetTable mCurrentTable;  // currently processed table
};


#endif // CHANGESETREADER_H
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#include "geodiff.h"
#include "geodifflogger.hpp"
#include "geodiffutils.hpp"
#include <iostream>

void StdoutLogger( GEODIFF_LoggerLevel level, const char *msg )
{
  switch ( level )
  {
    case LevelError:
      std::cerr << "Error: " << msg << std::endl;
      break;
    case LevelWarning:
      std::cout << "Warn: " << msg << std::endl;
      break;
    case LevelDebug:
      std::cout << "Debug: " << msg << std::endl;
      break;
    default: break;
  }
}

Logger::Logger()
{
  // Sort out which
  int envLevel = getEnvVarInt( "GEODIFF_LOGGER_LEVEL", 0 );
  if ( envLevel >= 0 && envLevel <= GEODIFF_LoggerLevel::LevelDebug )
  {
    setMaxLogLevel( static_cast<GEODIFF_LoggerLevel>( envLevel ) );
  }

  setCallback( &StdoutLogger );
}

void Logger::setCallback( GEODIFF_LoggerCallback loggerCallback )
{
  mLoggerCallback = loggerCallback;
}

void Logger::debug( const std::string &msg ) const
{
  log( GEODIFF_LoggerLevel::LevelDebug, msg );
}

void Logger::warn( const std::string &msg ) const
{
  log( GEODIFF_LoggerLevel::LevelWarning, msg );
}

void Logger::error( const std::string &msg ) const
{
  log( GEODIFF_LoggerLevel::LevelError, msg );
}

void Logger::error( const GeoDiffException &exp ) const
{
  log( GEODIFF_LoggerLevel::LevelError, exp.what() );
}

void Logger::info( const std::string &msg ) const
{
  log( GEODIFF_LoggerLevel::LevelInfo, msg );
}

void Logger::log( GEODIFF_LoggerLevel level, const std::string &msg ) const
{
  if ( mLoggerCallback )
  {
    // Check out if we want to print this message
    if ( static_cast<int>( level ) <= static_cast<int>( maxLogLevel() ) )
    {
      // Send to callback
      mLoggerCallback( level, msg.c_str() );
    }
  }
}
<file_sep>
DROP SCHEMA IF EXISTS gd_base CASCADE;

CREATE SCHEMA gd_base;

CREATE TABLE gd_base.simple ( "fid" SERIAL PRIMARY KEY, "geometry" GEOMETRY(POINT, 4326), "name" TEXT, "rating" INTEGER);

INSERT INTO gd_base.simple VALUES (1, ST_GeomFromText('Point (-1.08891928864569065 0.46101231190150482)', 4326), 'feature1', 1);
INSERT INTO gd_base.simple VALUES (2, ST_GeomFromText('Point (-0.36388508891928861 0.56224350205198359)', 4326), 'feature2', 2);
INSERT INTO gd_base.simple VALUES (3, ST_GeomFromText('Point (-0.73050615595075241 0.04240766073871405)', 4326), 'feature3', 3);
<file_sep># -*- coding: utf-8 -*-
"""
    :copyright: (c) 2019 <NAME>
    :license: MIT, see LICENSE for more details.
"""

from .testutils import *
import os
import shutil


class UnitTestsPythonApiCalls(GeoDiffTests):
    """Some quick tests of various API calls just to make sure they are not broken"""

    def test_global_settigs(self):
        print("********************************************************")
        print("PYTHON: test setting logger to None and Back")
        self.geodiff.set_logger_callback(None)
        self.geodiff.set_logger_callback(logger)

    def test_api_calls(self):
        print("********************************************************")
        print("PYTHON: test API calls")

        outdir = create_dir("api-calls")

        print("-- driver_api")
        if len(self.geodiff.drivers()) < 1:
            raise TestError("no drivers registered")

        if not self.geodiff.driver_is_registered("sqlite"):
            raise TestError("sqlite driver not registered")

        print("-- concat_changes")
        self.geodiff.concat_changes(
            [
                geodiff_test_dir() + "/concat/foo-insert-update-1.diff",
                geodiff_test_dir() + "/concat/foo-insert-update-2.diff",
            ],
            outdir + "/concat.diff",
        )

        self.geodiff.concat_changes(
            [
                geodiff_test_dir() + "/concat/bar-insert.diff",
                geodiff_test_dir() + "/concat/bar-update.diff",
                geodiff_test_dir() + "/concat/bar-delete.diff",
            ],
            outdir + "/concat.diff",
        )

        # This is not a valid concat - you delete feature and then update (deleted feature) and then insert it
        # But it should not crash. Ideally update is ignored (invalid step) and insert is applied
        # https://github.com/MerginMaps/geodiff/issues/174
        self.geodiff.concat_changes(
            [
                geodiff_test_dir() + "/concat/bar-delete.diff",
                geodiff_test_dir() + "/concat/bar-update.diff",
                geodiff_test_dir() + "/concat/bar-insert.diff",
            ],
            outdir + "/concat.diff",
        )

        print("-- make_copy")
        self.geodiff.make_copy(
            "sqlite",
            "",
            geodiff_test_dir() + "/base.gpkg",
            "sqlite",
            "",
            outdir + "/make-copy.gpkg",
        )

        print("-- make_copy_sqlite")
        self.geodiff.make_copy_sqlite(
            geodiff_test_dir() + "/base.gpkg", outdir + "/make-copy-sqlite.gpkg"
        )

        print("-- create_changeset_ex")
        self.geodiff.create_changeset_ex(
            "sqlite",
            "",
            geodiff_test_dir() + "/base.gpkg",
            geodiff_test_dir() + "/1_geopackage/modified_1_geom.gpkg",
            outdir + "/create-ex.diff",
        )

        print("-- apply_changeset_ex")
        self.geodiff.make_copy_sqlite(
            geodiff_test_dir() + "/base.gpkg", outdir + "/apply-ex.gpkg"
        )
        self.geodiff.apply_changeset_ex(
            "sqlite",
            "",
            outdir + "/apply-ex.gpkg",
            geodiff_test_dir() + "/1_geopackage/base-modified_1_geom.diff",
        )

        print("-- create_rebased_changeset_ex")
        self.geodiff.create_changeset_ex(
            "sqlite",
            "",
            geodiff_test_dir() + "/base.gpkg",
            geodiff_test_dir() + "/2_inserts/inserted_1_B.gpkg",
            outdir + "/rebased-ex-base2their.diff",
        )
        self.geodiff.create_rebased_changeset_ex(
            "sqlite",
            "",
            geodiff_test_dir() + "/base.gpkg",
            geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
            outdir + "/rebased-ex-base2their.diff",
            outdir + "/rebased-ex.diff",
            outdir + "/rebased-ex-conflicts.json",
        )

        print("-- rebase_ex")
        self.geodiff.make_copy_sqlite(
            geodiff_test_dir() + "/2_inserts/inserted_1_B.gpkg",
            outdir + "/rebase-ex.gpkg",
        )
        self.geodiff.rebase_ex(
            "sqlite",
            "",
            geodiff_test_dir() + "/base.gpkg",
            outdir + "/rebase-ex.gpkg",
            geodiff_test_dir() + "/2_inserts/base-inserted_1_A.diff",
            outdir + "/rebase-ex-conflicts.json",
        )

        print("-- dump_data")
        self.geodiff.dump_data(
            "sqlite", "", geodiff_test_dir() + "/base.gpkg", outdir + "/dump-data.diff"
        )

        print("-- schema")
        self.geodiff.schema(
            "sqlite", "", geodiff_test_dir() + "/base.gpkg", outdir + "/schema.json"
        )
<file_sep>
DROP SCHEMA IF EXISTS gd_updated_a CASCADE;

CREATE SCHEMA gd_updated_a;

CREATE TABLE gd_updated_a.simple ( "fid" SERIAL PRIMARY KEY, "geometry" GEOMETRY(POINT, 4326), "name" TEXT, "rating" INTEGER);

INSERT INTO gd_updated_a.simple VALUES (1, ST_GeomFromText('Point (-1.08891928864569065 0.46101231190150482)', 4326), 'feature1', 1);
INSERT INTO gd_updated_a.simple VALUES (2, ST_GeomFromText('Point (-0.92836978235933398 0.23787002896191123)', 4326), 'feature2', 9999);
INSERT INTO gd_updated_a.simple VALUES (3, ST_GeomFromText('Point (-0.73050615595075241 0.04240766073871405)', 4326), 'feature3', 3);
<file_sep>
DROP SCHEMA IF EXISTS gd_floats CASCADE;

CREATE SCHEMA gd_floats;

CREATE TABLE gd_floats.simple ( "fid" SERIAL PRIMARY KEY, "geometry" GEOMETRY(POINT, 4326), num DOUBLE PRECISION);

INSERT INTO gd_floats.simple VALUES (1, ST_GeomFromText('Point (-1.08891928864569065 0.46101231190150482)', 4326), 0.1);
INSERT INTO gd_floats.simple VALUES (2, ST_GeomFromText('Point (-0.36388508891928861 0.56224350205198359)', 4326), 0.0038970831662960364);
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef GEODIFFCONTEXT_H
#define GEODIFFCONTEXT_H

#include <string>
#include <vector>

#include "geodiff.h"
#include "geodifflogger.hpp"

class Context
{
  public:
    Context();

    Logger &logger();
    const Logger &logger() const;

    void setTablesToSkip( const std::vector<std::string> &tablesToSkip );
    bool isTableSkipped( const std::string &tableName ) const;

  private:
    Logger mLogger;
    std::vector<std::string> mTablesToSkip;
};


#endif // GEODIFFCONTEXT_H
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2022 <NAME>
*/

#include "gtest/gtest.h"
#include "geodiff_testutils.hpp"
#include "geodiff.h"

#include "changesetreader.h"
#include "sqliteutils.h"


TEST( GeometryUtilsTest, test_wkb_from_geometry )
{
  std::string changeset = pathjoin( testdir(), "1_geopackage", "base-modified_1_geom.diff" );

  ChangesetReader reader;
  EXPECT_TRUE( reader.open( changeset ) );

  ChangesetEntry entry;
  EXPECT_TRUE( reader.nextEntry( entry ) );
  EXPECT_EQ( entry.table->name, "gpkg_contents" );

  EXPECT_TRUE( reader.nextEntry( entry ) );
  EXPECT_EQ( entry.table->name, "simple" );
  EXPECT_EQ( entry.oldValues[1].type(), Value::TypeBlob );
  std::string gpkgWkb = entry.oldValues[1].getString();
  const char *c_gpkgWkb = gpkgWkb.c_str();
  size_t length = gpkgWkb.length();

  const char *c_wkb = nullptr;
  size_t wkbLength;

  int result = GEODIFF_createWkbFromGpkgHeader( testContext(), c_gpkgWkb, length, &c_wkb, &wkbLength );
  EXPECT_EQ( result, GEODIFF_SUCCESS );

  std::string wkb( c_wkb, wkbLength );

  // re-create GPKG envelope
  TableColumnInfo col;
  col.geomSrsId = 4326;
  col.geomType = "POINT";
  std::string binHead = createGpkgHeader( wkb, col );

  // fill envelope with geometry
  std::string gpb( binHead.size() + wkb.size(), 0 );
  memcpy( &gpb[0], binHead.data(), binHead.size() );
  memcpy( &gpb[binHead.size()], wkb.data(), wkb.size() );

  EXPECT_EQ( gpkgWkb, gpb );

  EXPECT_FALSE( reader.nextEntry( entry ) );
}

TEST( GeometryUtilsTest, test_wkb_from_geometry_errors )
{

  const char *c_wkb = new char[ 10 ];
  const char *res = nullptr;
  size_t wkbLength;

  int result = GEODIFF_createWkbFromGpkgHeader( nullptr, c_wkb, 1, &res, &wkbLength );
  EXPECT_EQ( result, GEODIFF_ERROR );

  result = GEODIFF_createWkbFromGpkgHeader( testContext(), nullptr, 1, &res, &wkbLength );
  EXPECT_EQ( result, GEODIFF_ERROR );

  result = GEODIFF_createWkbFromGpkgHeader( testContext(), c_wkb, 1, nullptr, &wkbLength );
  EXPECT_EQ( result, GEODIFF_ERROR );

  result = GEODIFF_createWkbFromGpkgHeader( testContext(), c_wkb, 1, &res, nullptr );
  EXPECT_EQ( result, GEODIFF_ERROR );

  delete []c_wkb;
}


int main( int argc, char **argv )
{
  testing::InitGoogleTest( &argc, argv );
  init_test();
  int ret =  RUN_ALL_TESTS();
  finalize_test();
  return ret;
}
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#include <cassert>

#include "driver.h"
#include "sqlitedriver.h"
#include "geodiff_config.hpp"

#ifdef HAVE_POSTGRES
#include "postgresdriver.h"
#endif

// define driver names
const std::string Driver::SQLITEDRIVERNAME = "sqlite";
const std::string Driver::POSTGRESDRIVERNAME = "postgres";

Driver::Driver( const Context *context )
  : mContext( context )
{
  assert( mContext );
}

Driver::~Driver() = default;

const Context *Driver::context() const
{
  return mContext;
}

std::vector<std::string> Driver::drivers()
{
  std::vector<std::string> names;
  names.push_back( SQLITEDRIVERNAME );
#ifdef HAVE_POSTGRES
  names.push_back( POSTGRESDRIVERNAME );
#endif
  return names;
}

bool Driver::driverIsRegistered( const std::string &driverName )
{
  const std::vector<std::string> drivers = Driver::drivers();
  return std::find( drivers.begin(), drivers.end(), driverName ) != drivers.end();
}

std::unique_ptr<Driver> Driver::createDriver( const Context *context, const std::string &driverName )
{
  if ( driverName == SQLITEDRIVERNAME )
  {
    return std::unique_ptr<Driver>( new SqliteDriver( context ) );
  }
#ifdef HAVE_POSTGRES
  if ( driverName == POSTGRESDRIVERNAME )
  {
    return std::unique_ptr<Driver>( new PostgresDriver( context ) );
  }
#endif
  return std::unique_ptr<Driver>();
}

DriverParametersMap Driver::sqliteParameters( const std::string &filenameBase, const std::string &filenameModified )
{
  DriverParametersMap conn;
  conn["base"] = filenameBase;
  conn["modified"] = filenameModified;
  return conn;
}

DriverParametersMap Driver::sqliteParametersSingleSource( const std::string &filename )
{
  DriverParametersMap conn;
  conn["base"] = filename;
  return conn;
}
<file_sep>/*
 GEODIFF - MIT License
 Copyright (C) 2020 <NAME>
*/

#ifndef CHANGESETGETVARINT_H
#define CHANGESETGETVARINT_H

#include <stdint.h>

// Contents of this file is entirely based on code from sqlite3
//
// the following two macro should be used for varint reading:
// - getVarint32


typedef uint8_t u8;
typedef uint32_t u32;
typedef uint64_t u64;


#define getVarint32(A,B)  \
  (u8)((*(A)<(u8)0x80)?((B)=(u32)*(A)),1:sqlite3GetVarint32((A),(u32 *)&(B)))


/*
** Bitmasks used by sqlite3GetVarint().  These precomputed constants
** are defined here rather than simply putting the constant expressions
** inline in order to work around bugs in the RVT compiler.
**
** SLOT_2_0     A mask for  (0x7f<<14) | 0x7f
**
** SLOT_4_2_0   A mask for  (0x7f<<28) | SLOT_2_0
*/
#define SLOT_2_0     0x001fc07f
#define SLOT_4_2_0   0xf01fc07f

/*
** SQLITE_MAX_U32 is a u64 constant that is the maximum u64 value
** that can be stored in a u32 without loss of data.  The value
** is 0x00000000ffffffff.  But because of quirks of some compilers, we
** have to specify the value in the less intuitive manner shown:
*/
#define SQLITE_MAX_U32  ((((u64)1)<<32)-1)


/*
** Read a 64-bit variable-length integer from memory starting at p[0].
** Return the number of bytes read.  The value is stored in *v.
*/
static u8 sqlite3GetVarint( const unsigned char *p, u64 *v )
{
  u32 a, b, s;

  if ( ( ( signed char * )p )[0] >= 0 )
  {
    *v = *p;
    return 1;
  }
  if ( ( ( signed char * )p )[1] >= 0 )
  {
    *v = ( ( u32 )( p[0] & 0x7f ) << 7 ) | p[1];
    return 2;
  }

  /* Verify that constants are precomputed correctly */
  assert( SLOT_2_0 == ( ( 0x7f << 14 ) | ( 0x7f ) ) );
  assert( SLOT_4_2_0 == ( ( 0xfU << 28 ) | ( 0x7f << 14 ) | ( 0x7f ) ) );

  a = ( ( u32 )p[0] ) << 14;
  b = p[1];
  p += 2;
  a |= *p;
  /* a: p0<<14 | p2 (unmasked) */
  if ( !( a & 0x80 ) )
  {
    a &= SLOT_2_0;
    b &= 0x7f;
    b = b << 7;
    a |= b;
    *v = a;
    return 3;
  }

  /* CSE1 from below */
  a &= SLOT_2_0;
  p++;
  b = b << 14;
  b |= *p;
  /* b: p1<<14 | p3 (unmasked) */
  if ( !( b & 0x80 ) )
  {
    b &= SLOT_2_0;
    /* moved CSE1 up */
    /* a &= (0x7f<<14)|(0x7f); */
    a = a << 7;
    a |= b;
    *v = a;
    return 4;
  }

  /* a: p0<<14 | p2 (masked) */
  /* b: p1<<14 | p3 (unmasked) */
  /* 1:save off p0<<21 | p1<<14 | p2<<7 | p3 (masked) */
  /* moved CSE1 up */
  /* a &= (0x7f<<14)|(0x7f); */
  b &= SLOT_2_0;
  s = a;
  /* s: p0<<14 | p2 (masked) */

  p++;
  a = a << 14;
  a |= *p;
  /* a: p0<<28 | p2<<14 | p4 (unmasked) */
  if ( !( a & 0x80 ) )
  {
    /* we can skip these cause they were (effectively) done above
    ** while calculating s */
    /* a &= (0x7f<<28)|(0x7f<<14)|(0x7f); */
    /* b &= (0x7f<<14)|(0x7f); */
    b = b << 7;
    a |= b;
    s = s >> 18;
    *v = ( ( u64 )s ) << 32 | a;
    return 5;
  }

  /* 2:save off p0<<21 | p1<<14 | p2<<7 | p3 (masked) */
  s = s << 7;
  s |= b;
  /* s: p0<<21 | p1<<14 | p2<<7 | p3 (masked) */

  p++;
  b = b << 14;
  b |= *p;
  /* b: p1<<28 | p3<<14 | p5 (unmasked) */
  if ( !( b & 0x80 ) )
  {
    /* we can skip this cause it was (effectively) done above in calc'ing s */
    /* b &= (0x7f<<28)|(0x7f<<14)|(0x7f); */
    a &= SLOT_2_0;
    a = a << 7;
    a |= b;
    s = s >> 18;
    *v = ( ( u64 )s ) << 32 | a;
    return 6;
  }

  p++;
  a = a << 14;
  a |= *p;
  /* a: p2<<28 | p4<<14 | p6 (unmasked) */
  if ( !( a & 0x80 ) )
  {
    a &= SLOT_4_2_0;
    b &= SLOT_2_0;
    b = b << 7;
    a |= b;
    s = s >> 11;
    *v = ( ( u64 )s ) << 32 | a;
    return 7;
  }

  /* CSE2 from below */
  a &= SLOT_2_0;
  p++;
  b = b << 14;
  b |= *p;
  /* b: p3<<28 | p5<<14 | p7 (unmasked) */
  if ( !( b & 0x80 ) )
  {
    b &= SLOT_4_2_0;
    /* moved CSE2 up */
    /* a &= (0x7f<<14)|(0x7f); */
    a = a << 7;
    a |= b;
    s = s >> 4;
    *v = ( ( u64 )s ) << 32 | a;
    return 8;
  }

  p++;
  a = a << 15;
  a |= *p;
  /* a: p4<<29 | p6<<15 | p8 (unmasked) */

  /* moved CSE2 up */
  /* a &= (0x7f<<29)|(0x7f<<15)|(0xff); */
  b &= SLOT_2_0;
  b = b << 8;
  a |= b;

  s = s << 4;
  b = p[-4];
  b &= 0x7f;
  b = b >> 3;
  s |= b;

  *v = ( ( u64 )s ) << 32 | a;

  return 9;
}

/*
** Read a 32-bit variable-length integer from memory starting at p[0].
** Return the number of bytes read.  The value is stored in *v.
**
** If the varint stored in p[0] is larger than can fit in a 32-bit unsigned
** integer, then set *v to 0xffffffff.
**
** A MACRO version, getVarint32, is provided which inlines the
** single-byte case.  All code should use the MACRO version as
** this function assumes the single-byte case has already been handled.
*/
static u8 sqlite3GetVarint32( const unsigned char *p, u32 *v )
{
  u32 a, b;

  /* The 1-byte case.  Overwhelmingly the most common.  Handled inline
  ** by the getVarin32() macro */
  a = *p;
  /* a: p0 (unmasked) */
#ifndef getVarint32
  if ( !( a & 0x80 ) )
  {
    /* Values between 0 and 127 */
    *v = a;
    return 1;
  }
#endif

  /* The 2-byte case */
  p++;
  b = *p;
  /* b: p1 (unmasked) */
  if ( !( b & 0x80 ) )
  {
    /* Values between 128 and 16383 */
    a &= 0x7f;
    a = a << 7;
    *v = a | b;
    return 2;
  }

  /* The 3-byte case */
  p++;
  a = a << 14;
  a |= *p;
  /* a: p0<<14 | p2 (unmasked) */
  if ( !( a & 0x80 ) )
  {
    /* Values between 16384 and 2097151 */
    a &= ( 0x7f << 14 ) | ( 0x7f );
    b &= 0x7f;
    b = b << 7;
    *v = a | b;
    return 3;
  }

  {
    u64 v64;
    u8 n;

    p -= 2;
    n = sqlite3GetVarint( p, &v64 );
    assert( n > 3 && n <= 9 );
    if ( ( v64 & SQLITE_MAX_U32 ) != v64 )
    {
      *v = 0xffffffff;
    }
    else
    {
      *v = ( u32 )v64;
    }
    return n;
  }
}

#endif // CHANGESETGETVARINT_H
