Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,46 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "CMake Configure (C++17)",
"type": "shell",
"command": "cmake",
"args": [
"-B",
"${workspaceFolder}/build",
"-S",
"${workspaceFolder}",
"-DSQLITE_ORM_ENABLE_CXX_17=ON",
"-DBUILD_TESTING=ON",
"-DCMAKE_BUILD_TYPE=Debug"
],
"group": "build",
"problemMatcher": [],
"presentation": {
"reveal": "always",
"panel": "shared"
}
},
{
"label": "CMake Build (C++17)",
"type": "shell",
"command": "cmake",
"args": [
"--build",
"${workspaceFolder}/build",
"--parallel"
],
"group": {
"kind": "build",
"isDefault": true
},
"dependsOn": "CMake Configure (C++17)",
"problemMatcher": "$gcc",
"presentation": {
"reveal": "always",
"panel": "shared"
}
},
{
"label": "Run unit_tests",
"type": "shell",
Expand Down
3 changes: 3 additions & 0 deletions dev/implementations/storage_definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ namespace sqlite_orm::internal {
this->drop_create_with_loss(db, table);
}
res = schema_stat;
} else if (schema_stat == sync_schema_result::dropped_and_recreated_with_data_loss) {
this->drop_create_with_loss(db, table);
res = schema_stat;
}
}
}
Expand Down
20 changes: 18 additions & 2 deletions dev/storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -1132,6 +1132,7 @@ namespace sqlite_orm::internal {

auto dbTableInfo = this->pragma.table_xinfo(table.name);
auto res = sync_schema_result::already_in_sync;
bool canPreserveData = true;

// first let's see if table with such name exists..
auto gottaCreateTable = !this->table_exists(db, table.name);
Expand Down Expand Up @@ -1163,7 +1164,20 @@ namespace sqlite_orm::internal {
}
}
if (gottaCreateTable) {
res = sync_schema_result::dropped_and_recreated;
// check if any new columns prevent data preservation
for (const table_xinfo* colInfo: columnsToAdd) {
if (!table.find_column_generated_storage_type(colInfo->name)) {
if (colInfo->notnull && colInfo->dflt_value.empty()) {
canPreserveData = false;
if (attempt_to_preserve) {
*attempt_to_preserve = false;
};
break;
}
}
}
res = canPreserveData ? sync_schema_result::dropped_and_recreated
: sync_schema_result::dropped_and_recreated_with_data_loss;
} else {
if (!columnsToAdd.empty()) {
// extra storage columns than table columns
Expand All @@ -1179,6 +1193,7 @@ namespace sqlite_orm::internal {
} else {
if (colInfo->notnull && colInfo->dflt_value.empty()) {
gottaCreateTable = true;
canPreserveData = false;
// no matter if preserve is true or false, there is no way to preserve data, so we wont try!
if (attempt_to_preserve) {
*attempt_to_preserve = false;
Expand All @@ -1194,7 +1209,8 @@ namespace sqlite_orm::internal {
res = sync_schema_result::new_columns_added;
}
} else {
res = sync_schema_result::dropped_and_recreated;
res = canPreserveData ? sync_schema_result::dropped_and_recreated
: sync_schema_result::dropped_and_recreated_with_data_loss;
}
} else {
if (res != sync_schema_result::old_columns_removed) {
Expand Down
8 changes: 4 additions & 4 deletions dev/storage_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -1123,11 +1123,11 @@ namespace sqlite_orm::internal {
(dbColumnInfo.hidden == 0) == (storageColumnInfo.hidden == 0);
if (!columnsAreEqual) {
notEqual = true;
break;
} else {
dbTableInfo.erase(dbColumnInfoIt);
storageTableInfo.erase(storageTableInfo.begin() + storageColumnInfoIndex);
--storageColumnInfoIndex;
}
dbTableInfo.erase(dbColumnInfoIt);
storageTableInfo.erase(storageTableInfo.begin() + storageColumnInfoIndex);
--storageColumnInfoIndex;
} else {
columnsToAdd.push_back(&storageColumnInfo);
}
Expand Down
15 changes: 12 additions & 3 deletions dev/sync_schema_result.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,18 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
/**
* old table is dropped and new is recreated. Reasons :
* 1. delete excess columns in the table than storage if preseve = false
* 2. Lacking columns in the table cannot be added due to NULL and DEFAULT constraint
* 3. Reasons 1 and 2 both together
* 4. data_type mismatch between table and storage.
* 2. Reasons 1 and 4 both together
* 3. data_type mismatch between table and storage.
* Data is preserved through a backup table when preserve = true.
*/
dropped_and_recreated,

/**
* old table is dropped and new is recreated with data loss.
* Data cannot be preserved because a new NOT NULL column without
* a default value is being added, making backup impossible.
*/
dropped_and_recreated_with_data_loss,
};

inline std::ostream& operator<<(std::ostream& os, sync_schema_result value) {
Expand All @@ -57,6 +64,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
return os << "old excess columns removed and new columns added";
case sync_schema_result::dropped_and_recreated:
return os << "old table dropped and recreated";
case sync_schema_result::dropped_and_recreated_with_data_loss:
return os << "old table dropped and recreated with data loss";
}
return os;
}
Expand Down
46 changes: 37 additions & 9 deletions include/sqlite_orm/sqlite_orm.h
Original file line number Diff line number Diff line change
Expand Up @@ -12390,11 +12390,18 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
/**
* old table is dropped and new is recreated. Reasons :
* 1. delete excess columns in the table than storage if preseve = false
* 2. Lacking columns in the table cannot be added due to NULL and DEFAULT constraint
* 3. Reasons 1 and 2 both together
* 4. data_type mismatch between table and storage.
* 2. Reasons 1 and 4 both together
* 3. data_type mismatch between table and storage.
* Data is preserved through a backup table when preserve = true.
*/
dropped_and_recreated,

/**
* old table is dropped and new is recreated with data loss.
* Data cannot be preserved because a new NOT NULL column without
* a default value is being added, making backup impossible.
*/
dropped_and_recreated_with_data_loss,
};

inline std::ostream& operator<<(std::ostream& os, sync_schema_result value) {
Expand All @@ -12411,6 +12418,8 @@ SQLITE_ORM_EXPORT namespace sqlite_orm {
return os << "old excess columns removed and new columns added";
case sync_schema_result::dropped_and_recreated:
return os << "old table dropped and recreated";
case sync_schema_result::dropped_and_recreated_with_data_loss:
return os << "old table dropped and recreated with data loss";
}
return os;
}
Expand Down Expand Up @@ -19784,11 +19793,11 @@ namespace sqlite_orm::internal {
(dbColumnInfo.hidden == 0) == (storageColumnInfo.hidden == 0);
if (!columnsAreEqual) {
notEqual = true;
break;
} else {
dbTableInfo.erase(dbColumnInfoIt);
storageTableInfo.erase(storageTableInfo.begin() + storageColumnInfoIndex);
--storageColumnInfoIndex;
}
dbTableInfo.erase(dbColumnInfoIt);
storageTableInfo.erase(storageTableInfo.begin() + storageColumnInfoIndex);
--storageColumnInfoIndex;
} else {
columnsToAdd.push_back(&storageColumnInfo);
}
Expand Down Expand Up @@ -25037,6 +25046,7 @@ namespace sqlite_orm::internal {

auto dbTableInfo = this->pragma.table_xinfo(table.name);
auto res = sync_schema_result::already_in_sync;
bool canPreserveData = true;

// first let's see if table with such name exists..
auto gottaCreateTable = !this->table_exists(db, table.name);
Expand Down Expand Up @@ -25068,7 +25078,20 @@ namespace sqlite_orm::internal {
}
}
if (gottaCreateTable) {
res = sync_schema_result::dropped_and_recreated;
// check if any new columns prevent data preservation
for (const table_xinfo* colInfo: columnsToAdd) {
if (!table.find_column_generated_storage_type(colInfo->name)) {
if (colInfo->notnull && colInfo->dflt_value.empty()) {
canPreserveData = false;
if (attempt_to_preserve) {
*attempt_to_preserve = false;
};
break;
}
}
}
res = canPreserveData ? sync_schema_result::dropped_and_recreated
: sync_schema_result::dropped_and_recreated_with_data_loss;
} else {
if (!columnsToAdd.empty()) {
// extra storage columns than table columns
Expand All @@ -25084,6 +25107,7 @@ namespace sqlite_orm::internal {
} else {
if (colInfo->notnull && colInfo->dflt_value.empty()) {
gottaCreateTable = true;
canPreserveData = false;
// no matter if preserve is true or false, there is no way to preserve data, so we wont try!
if (attempt_to_preserve) {
*attempt_to_preserve = false;
Expand All @@ -25099,7 +25123,8 @@ namespace sqlite_orm::internal {
res = sync_schema_result::new_columns_added;
}
} else {
res = sync_schema_result::dropped_and_recreated;
res = canPreserveData ? sync_schema_result::dropped_and_recreated
: sync_schema_result::dropped_and_recreated_with_data_loss;
}
} else {
if (res != sync_schema_result::old_columns_removed) {
Expand Down Expand Up @@ -26035,6 +26060,9 @@ namespace sqlite_orm::internal {
this->drop_create_with_loss(db, table);
}
res = schema_stat;
} else if (schema_stat == sync_schema_result::dropped_and_recreated_with_data_loss) {
this->drop_create_with_loss(db, table);
res = schema_stat;
}
}
}
Expand Down
75 changes: 74 additions & 1 deletion tests/sync_schema_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ TEST_CASE("sync_schema") {
}
REQUIRE(syncSchemaSimulateRes == syncSchemaRes);
decltype(syncSchemaRes) expected{
{tableName, sync_schema_result::dropped_and_recreated},
{tableName, sync_schema_result::dropped_and_recreated_with_data_loss},
};
REQUIRE(syncSchemaRes == expected);
auto users = storage.get_all<User>();
Expand Down Expand Up @@ -469,6 +469,79 @@ TEST_CASE("sync_schema") {
}
}

// https://github.com/fnc12/sqlite_orm/issues/1462
TEST_CASE("Distinguish dropped_and_recreated with and without backup") {
struct MyTableRecord {
int id = 0;
std::string name;
};

auto storagePath = "issue1462.sqlite";
std::remove(storagePath);

SECTION("sequential schema changes (exact issue scenario)") {
// Step 1: create initial table with a single column, insert data
{
auto storage = make_storage(storagePath, make_table("MyTable", make_column("id", &MyTableRecord::id)));
storage.sync_schema(true);
storage.insert(MyTableRecord{1, ""});
storage.insert(MyTableRecord{2, ""});
}
// Step 2: add primary key — data should be preserved
{
auto storage =
make_storage(storagePath, make_table("MyTable", make_column("id", &MyTableRecord::id, primary_key())));
auto simulateRes = storage.sync_schema_simulate(true);
auto syncRes = storage.sync_schema(true);
REQUIRE(simulateRes == syncRes);
REQUIRE(syncRes.at("MyTable") == sync_schema_result::dropped_and_recreated);

auto allRecords = storage.get_all<MyTableRecord>();
REQUIRE(allRecords.size() == 2);
}
// Step 3: add NOT NULL column without default — data should be lost
{
auto storage = make_storage(storagePath,
make_table("MyTable",
make_column("id", &MyTableRecord::id, primary_key()),
make_column("name", &MyTableRecord::name)));
auto simulateRes = storage.sync_schema_simulate(true);
auto syncRes = storage.sync_schema(true);
REQUIRE(simulateRes == syncRes);
REQUIRE(syncRes.at("MyTable") == sync_schema_result::dropped_and_recreated_with_data_loss);

auto allRecords = storage.get_all<MyTableRecord>();
REQUIRE(allRecords.empty());
}
}

SECTION("combined pk change and new NOT NULL column in one step") {
// create initial table with a single column, insert data
{
auto storage = make_storage(storagePath, make_table("MyTable", make_column("id", &MyTableRecord::id)));
storage.sync_schema(true);
storage.insert(MyTableRecord{1, ""});
storage.insert(MyTableRecord{2, ""});
}
// add primary key AND NOT NULL column without default at once — data should be lost
{
auto storage = make_storage(storagePath,
make_table("MyTable",
make_column("id", &MyTableRecord::id, primary_key()),
make_column("name", &MyTableRecord::name)));
auto simulateRes = storage.sync_schema_simulate(true);
auto syncRes = storage.sync_schema(true);
REQUIRE(simulateRes == syncRes);
REQUIRE(syncRes.at("MyTable") == sync_schema_result::dropped_and_recreated_with_data_loss);

auto allRecords = storage.get_all<MyTableRecord>();
REQUIRE(allRecords.empty());
}
}

std::remove(storagePath);
}

TEST_CASE("sync_schema_simulate") {
struct Cols {
int Col1 = 0;
Expand Down
Loading