Skip to content

Commit

Permalink
Merge c858860 into 35dcf7a
Browse files Browse the repository at this point in the history
  • Loading branch information
PeterPetrik committed Sep 20, 2019
2 parents 35dcf7a + c858860 commit 162546d
Show file tree
Hide file tree
Showing 13 changed files with 318 additions and 11 deletions.
5 changes: 5 additions & 0 deletions geodiff/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,13 @@ set(libgpkg_src
${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()
Expand Down
33 changes: 30 additions & 3 deletions geodiff/src/geodiff.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,38 @@ int GEODIFF_createChangeset( const char *base, const char *modified, const char
Sqlite3Session session;
session.create( db, "main" );

std::vector<std::string> tableNames;
tables( db, tableNames );
// names are sorted and gpkg tables are filtered out
std::vector<std::string> mainTableNames;
tables( db, "main", mainTableNames );

for ( const std::string &table : tableNames )
// names are sorted and gpkg tables are filtered out
std::vector<std::string> auxTableNames;
tables( db, "aux", auxTableNames );

if ( auxTableNames.size() != mainTableNames.size() )
{
Logger::instance().error( "Modified does contain different number of tables than base in GEODIFF_createChangeset" );
return GEODIFF_UNSUPPORTED_CHANGE;
}

for ( size_t i = 0; i < mainTableNames.size(); ++i )
{
const std::string &table = mainTableNames.at( i );
const std::string &auxTable = auxTableNames.at( i );
if ( auxTable != table )
{
Logger::instance().error( "Modified renamed table " + table + " to " + auxTable + " in GEODIFF_createChangeset" );
return GEODIFF_UNSUPPORTED_CHANGE;
}

std::string errMsg;
bool hasSameSchema = has_same_table_schema( db, table, errMsg );
if ( !hasSameSchema )
{
Logger::instance().error( errMsg + " in GEODIFF_createChangeset" );
return GEODIFF_UNSUPPORTED_CHANGE;
}

int rc = sqlite3session_attach( session.get(), table.c_str() );
if ( rc )
{
Expand Down
2 changes: 2 additions & 0 deletions geodiff/src/geodiff.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#define GEODIFF_SUCCESS 0 //!< Success
#define GEODIFF_ERROR 1 //!< General error
#define GEODIFF_CONFICTS 2 //!< The changeset couldn't be applied directly
#define GEODIFF_UNSUPPORTED_CHANGE 3 //! The diff for such entry is unsupported/not-implemented

/*
** Make sure we can call this stuff from C++.
*/
Expand Down
164 changes: 159 additions & 5 deletions geodiff/src/geodiffutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -794,13 +794,12 @@ void triggers( std::shared_ptr<Sqlite3Db> db, std::vector<std::string> &triggerN
statament.close();
}

void tables( std::shared_ptr<Sqlite3Db> db, std::vector<std::string> &tableNames )
void tables( std::shared_ptr<Sqlite3Db> db,
const std::string &dbName,
std::vector<std::string> &tableNames )
{
tableNames.clear();
std::string all_tables_sql = "SELECT name FROM main.sqlite_master\n"
" WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
" UNION\n"
"SELECT name FROM aux.sqlite_master\n"
std::string all_tables_sql = "SELECT name FROM " + dbName + ".sqlite_master\n"
" WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
" ORDER BY name";
Sqlite3Stmt statament;
Expand Down Expand Up @@ -836,4 +835,159 @@ void tables( std::shared_ptr<Sqlite3Db> db, std::vector<std::string> &tableNames

tableNames.push_back( tableName );
}

// result is ordered by name
}

/*
* inspired by sqldiff.c function: columnNames()
*/
static std::vector<std::string> _columnNames(
std::shared_ptr<Sqlite3Db> db,
const char *zDb, /* Database ("main" or "aux") to query */
const std::string &tableName /* Name of table to return details of */
)
{
std::vector<std::string> az; /* List of column names to be returned */
int naz = 0; /* Number of entries in az[] */
Sqlite3Stmt pStmt; /* SQL statement being run */
std::string zPkIdxName; /* Name of the PRIMARY KEY index */
int truePk = 0; /* PRAGMA table_info indentifies the PK to use */
int nPK = 0; /* Number of PRIMARY KEY columns */
int i, j; /* Loop counters */

/* Figure out what the true primary key is for the table.
** * For WITHOUT ROWID tables, the true primary key is the same as
** the schema PRIMARY KEY, which is guaranteed to be present.
** * For rowid tables with an INTEGER PRIMARY KEY, the true primary
** key is the INTEGER PRIMARY KEY.
** * For all other rowid tables, the rowid is the true primary key.
*/
const char *zTab = tableName.c_str();
pStmt.prepare( db, "PRAGMA %s.index_list=%Q", zDb, zTab );
while ( SQLITE_ROW == sqlite3_step( pStmt.get() ) )
{
if ( sqlite3_stricmp( ( const char * )sqlite3_column_text( pStmt.get(), 3 ), "pk" ) == 0 )
{
zPkIdxName = ( const char * ) sqlite3_column_text( pStmt.get(), 1 );
break;
}
}
pStmt.close();

if ( !zPkIdxName.empty() )
{
int nKey = 0;
int nCol = 0;
truePk = 0;
pStmt.prepare( db, "PRAGMA %s.index_xinfo=%Q", zDb, zPkIdxName.c_str() );
while ( SQLITE_ROW == sqlite3_step( pStmt.get() ) )
{
nCol++;
if ( sqlite3_column_int( pStmt.get(), 5 ) ) { nKey++; continue; }
if ( sqlite3_column_int( pStmt.get(), 1 ) >= 0 ) truePk = 1;
}
if ( nCol == nKey ) truePk = 1;
if ( truePk )
{
nPK = nKey;
}
else
{
nPK = 1;
}
pStmt.close();
}
else
{
truePk = 1;
nPK = 1;
}
pStmt.prepare( db, "PRAGMA %s.table_info=%Q", zDb, zTab );

naz = nPK;
az.resize( naz );
while ( SQLITE_ROW == sqlite3_step( pStmt.get() ) )
{
int iPKey;
std::string name = ( char * )sqlite3_column_text( pStmt.get(), 1 );
if ( truePk && ( iPKey = sqlite3_column_int( pStmt.get(), 5 ) ) > 0 )
{
az[iPKey - 1] = name;
}
else
{
az.push_back( name );
}
}
pStmt.close();

/* If this table has an implicit rowid for a PK, figure out how to refer
** to it. There are three options - "rowid", "_rowid_" and "oid". Any
** of these will work, unless the table has an explicit column of the
** same name. */
if ( az[0].empty() )
{
std::vector<std::string> azRowid = { "rowid", "_rowid_", "oid" };
for ( i = 0; i < azRowid.size(); i++ )
{
for ( j = 1; j < naz; j++ )
{
if ( az[j] == azRowid[i] ) break;
}
if ( j >= naz )
{
az[0] = azRowid[i];
break;
}
}
if ( az[0].empty() )
{
az.clear();
}
}

return az;
}

bool has_same_table_schema( std::shared_ptr<Sqlite3Db> db, const std::string &tableName, std::string &errStr )
{
// inspired by sqldiff.c function: static void summarize_one_table(const char *zTab, FILE *out);
if ( sqlite3_table_column_metadata( db->get(), "main", tableName.c_str(), 0, 0, 0, 0, 0, 0 ) )
{
/* Table missing from source */
errStr = tableName + " missing from first database";
return false;
}

std::vector<std::string> az = _columnNames( db, "main", tableName );
std::vector<std::string> az2 = _columnNames( db, "aux", tableName );
if ( az.size() != az2.size() )
{
errStr = "Table " + tableName + " has different number of columns";
return false;
}


for ( size_t n = 0; n < az.size(); ++n )
{
if ( az[n] != az2[n] )
{
errStr = "Table " + tableName + " has different name of columns: " + az[n] + " vs " + az2[n];
return false;
}

/* no need to check type:
from sqlite.h:
SQLite uses dynamic run-time typing. So just because a column
is declared to contain a particular type does not mean that the
data stored in that column is of the declared type. SQLite is
strongly typed, but the typing is dynamic not static. ^Type
is associated with individual values, not with the containers
used to hold those values.
*/
}

return true;
}
13 changes: 11 additions & 2 deletions geodiff/src/geodiffutils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,18 @@ bool startsWith( const std::string &str, const std::string &substr );

// SOME SQL

void triggers( std::shared_ptr<Sqlite3Db> db, std::vector<std::string> &triggerNames, std::vector<std::string> &triggerCmds );
void tables( std::shared_ptr<Sqlite3Db> db, std::vector<std::string> &tableNames );
void triggers( std::shared_ptr<Sqlite3Db> db,
std::vector<std::string> &triggerNames,
std::vector<std::string> &triggerCmds );

void tables( std::shared_ptr<Sqlite3Db> db,
const std::string &dbName,
std::vector<std::string> &tableNames );


bool has_same_table_schema( std::shared_ptr<Sqlite3Db> db,
const std::string &tableName,
std::string &errStr );

// WRITE CHANGESET API

Expand Down
1 change: 1 addition & 0 deletions geodiff/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ ENDMACRO (ADD_GEODIFF_TEST)
SET(TESTS
test_single_commit.cpp
test_concurrent_commits.cpp
test_modified_scheme.cpp
)

FOREACH(TESTSRC ${TESTS})
Expand Down
109 changes: 109 additions & 0 deletions geodiff/tests/test_modified_scheme.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
GEODIFF - MIT License
Copyright (C) 2019 Peter Petrik
*/

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

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( base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_UNSUPPORTED_CHANGE );
}

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( base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_UNSUPPORTED_CHANGE );
}

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( base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_UNSUPPORTED_CHANGE );
}

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( base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_UNSUPPORTED_CHANGE );
}

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( base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_UNSUPPORTED_CHANGE );
}

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( base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_UNSUPPORTED_CHANGE );
}

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

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

// this is supported since sqlite3 uses dynamic run-time typing
ASSERT_EQ( GEODIFF_createChangeset( base.c_str(), modified.c_str(), changeset.c_str() ), GEODIFF_SUCCESS );
}

int main( int argc, char **argv )
{
testing::InitGoogleTest( &argc, argv );
init_test();
int ret = RUN_ALL_TESTS();
finalize_test();
return ret;
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion scripts/ci/osx/build.bash
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ make
ctest -VV

cd ../../
GEODIFFLIB=`pwd`/../geodiff/build_osx/libgeodiff.dylib nose2
GEODIFFLIB=`pwd`/geodiff/build_osx/libgeodiff.dylib nose2

0 comments on commit 162546d

Please sign in to comment.