summaryrefslogtreecommitdiff
path: root/odb-tests/evolution
diff options
context:
space:
mode:
Diffstat (limited to 'odb-tests/evolution')
-rw-r--r--odb-tests/evolution/.gitignore15
-rw-r--r--odb-tests/evolution/add-column/buildfile63
-rw-r--r--odb-tests/evolution/add-column/driver.cxx130
-rw-r--r--odb-tests/evolution/add-column/model.hxx38
-rw-r--r--odb-tests/evolution/add-column/test1.hxx9
-rw-r--r--odb-tests/evolution/add-column/test2.hxx11
-rw-r--r--odb-tests/evolution/add-column/test3.hxx11
-rw-r--r--odb-tests/evolution/add-column/testscript58
-rw-r--r--odb-tests/evolution/add-foreign-key/buildfile64
-rw-r--r--odb-tests/evolution/add-foreign-key/driver.cxx178
-rw-r--r--odb-tests/evolution/add-foreign-key/model.hxx66
-rw-r--r--odb-tests/evolution/add-foreign-key/test1.hxx9
-rw-r--r--odb-tests/evolution/add-foreign-key/test2.hxx11
-rw-r--r--odb-tests/evolution/add-foreign-key/test3.hxx11
-rw-r--r--odb-tests/evolution/add-foreign-key/testscript58
-rw-r--r--odb-tests/evolution/add-index/buildfile63
-rw-r--r--odb-tests/evolution/add-index/driver.cxx170
-rw-r--r--odb-tests/evolution/add-index/model.hxx33
-rw-r--r--odb-tests/evolution/add-index/test1.hxx9
-rw-r--r--odb-tests/evolution/add-index/test2.hxx11
-rw-r--r--odb-tests/evolution/add-index/test3.hxx11
-rw-r--r--odb-tests/evolution/add-index/testscript58
-rw-r--r--odb-tests/evolution/add-table/buildfile63
-rw-r--r--odb-tests/evolution/add-table/driver.cxx145
-rw-r--r--odb-tests/evolution/add-table/model.hxx48
-rw-r--r--odb-tests/evolution/add-table/test1.hxx9
-rw-r--r--odb-tests/evolution/add-table/test2.hxx11
-rw-r--r--odb-tests/evolution/add-table/test3.hxx11
-rw-r--r--odb-tests/evolution/add-table/testscript58
-rw-r--r--odb-tests/evolution/alter-column/buildfile63
-rw-r--r--odb-tests/evolution/alter-column/driver.cxx164
-rw-r--r--odb-tests/evolution/alter-column/model.hxx56
-rw-r--r--odb-tests/evolution/alter-column/test1.hxx9
-rw-r--r--odb-tests/evolution/alter-column/test2.hxx11
-rw-r--r--odb-tests/evolution/alter-column/test3.hxx11
-rw-r--r--odb-tests/evolution/alter-column/testscript59
-rw-r--r--odb-tests/evolution/combined/buildfile64
-rw-r--r--odb-tests/evolution/combined/driver.cxx162
-rw-r--r--odb-tests/evolution/combined/model.hxx174
-rw-r--r--odb-tests/evolution/combined/test1.hxx9
-rw-r--r--odb-tests/evolution/combined/test2.hxx11
-rw-r--r--odb-tests/evolution/combined/test3.hxx11
-rw-r--r--odb-tests/evolution/combined/testscript58
-rw-r--r--odb-tests/evolution/data/buildfile63
-rw-r--r--odb-tests/evolution/data/driver.cxx179
-rw-r--r--odb-tests/evolution/data/model.hxx45
-rw-r--r--odb-tests/evolution/data/test1.hxx9
-rw-r--r--odb-tests/evolution/data/test2.hxx11
-rw-r--r--odb-tests/evolution/data/test3.hxx11
-rw-r--r--odb-tests/evolution/data/testscript58
-rw-r--r--odb-tests/evolution/drop-column/buildfile64
-rw-r--r--odb-tests/evolution/drop-column/driver.cxx131
-rw-r--r--odb-tests/evolution/drop-column/model.hxx59
-rw-r--r--odb-tests/evolution/drop-column/test1.hxx9
-rw-r--r--odb-tests/evolution/drop-column/test2.hxx11
-rw-r--r--odb-tests/evolution/drop-column/test3.hxx11
-rw-r--r--odb-tests/evolution/drop-column/testscript58
-rw-r--r--odb-tests/evolution/drop-foreign-key/buildfile64
-rw-r--r--odb-tests/evolution/drop-foreign-key/driver.cxx149
-rw-r--r--odb-tests/evolution/drop-foreign-key/model.hxx50
-rw-r--r--odb-tests/evolution/drop-foreign-key/test1.hxx9
-rw-r--r--odb-tests/evolution/drop-foreign-key/test2.hxx11
-rw-r--r--odb-tests/evolution/drop-foreign-key/test3.hxx11
-rw-r--r--odb-tests/evolution/drop-foreign-key/testscript59
-rw-r--r--odb-tests/evolution/drop-index/buildfile63
-rw-r--r--odb-tests/evolution/drop-index/driver.cxx159
-rw-r--r--odb-tests/evolution/drop-index/model.hxx33
-rw-r--r--odb-tests/evolution/drop-index/test1.hxx9
-rw-r--r--odb-tests/evolution/drop-index/test2.hxx11
-rw-r--r--odb-tests/evolution/drop-index/test3.hxx11
-rw-r--r--odb-tests/evolution/drop-index/testscript58
-rw-r--r--odb-tests/evolution/drop-table/buildfile63
-rw-r--r--odb-tests/evolution/drop-table/driver.cxx173
-rw-r--r--odb-tests/evolution/drop-table/model.hxx94
-rw-r--r--odb-tests/evolution/drop-table/test1.hxx9
-rw-r--r--odb-tests/evolution/drop-table/test2.hxx11
-rw-r--r--odb-tests/evolution/drop-table/test3.hxx11
-rw-r--r--odb-tests/evolution/drop-table/testscript58
-rw-r--r--odb-tests/evolution/embedded/buildfile59
-rw-r--r--odb-tests/evolution/embedded/driver.cxx185
-rw-r--r--odb-tests/evolution/embedded/model.hxx45
-rw-r--r--odb-tests/evolution/embedded/test1.hxx9
-rw-r--r--odb-tests/evolution/embedded/test2.hxx11
-rw-r--r--odb-tests/evolution/embedded/test3.hxx11
-rw-r--r--odb-tests/evolution/embedded/testscript21
-rw-r--r--odb-tests/evolution/soft-add/buildfile64
-rw-r--r--odb-tests/evolution/soft-add/driver.cxx2224
-rw-r--r--odb-tests/evolution/soft-add/model.hxx504
-rw-r--r--odb-tests/evolution/soft-add/test1.hxx9
-rw-r--r--odb-tests/evolution/soft-add/test2.hxx11
-rw-r--r--odb-tests/evolution/soft-add/test3.hxx11
-rw-r--r--odb-tests/evolution/soft-add/testscript58
-rw-r--r--odb-tests/evolution/soft-delete/buildfile65
-rw-r--r--odb-tests/evolution/soft-delete/driver.cxx2207
-rw-r--r--odb-tests/evolution/soft-delete/model.hxx512
-rw-r--r--odb-tests/evolution/soft-delete/test1.hxx9
-rw-r--r--odb-tests/evolution/soft-delete/test2.hxx11
-rw-r--r--odb-tests/evolution/soft-delete/test3.hxx11
-rw-r--r--odb-tests/evolution/soft-delete/testscript58
-rw-r--r--odb-tests/evolution/version/buildfile63
-rw-r--r--odb-tests/evolution/version/driver.cxx161
-rw-r--r--odb-tests/evolution/version/model.hxx45
-rw-r--r--odb-tests/evolution/version/test1.hxx9
-rw-r--r--odb-tests/evolution/version/test2.hxx11
-rw-r--r--odb-tests/evolution/version/test3.hxx11
-rw-r--r--odb-tests/evolution/version/testscript58
106 files changed, 10582 insertions, 0 deletions
diff --git a/odb-tests/evolution/.gitignore b/odb-tests/evolution/.gitignore
new file mode 100644
index 0000000..9a410c7
--- /dev/null
+++ b/odb-tests/evolution/.gitignore
@@ -0,0 +1,15 @@
+# ODB-generated files.
+#
+test1-odb.?xx
+test1.sql
+model.xml
+
+test2-odb.?xx
+test2.sql
+
+test3-odb.?xx
+test3.sql
+test3-002-post.sql
+test3-002-pre.sql
+test3-003-post.sql
+test3-003-pre.sql
diff --git a/odb-tests/evolution/add-column/buildfile b/odb-tests/evolution/add-column/buildfile
new file mode 100644
index 0000000..efa6b6d
--- /dev/null
+++ b/odb-tests/evolution/add-column/buildfile
@@ -0,0 +1,63 @@
+# file : evolution/add-column/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+
+import libs = libodb-$db%lib{odb-$db}
+import libs += lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the odb_compile ad hoc rule (see build/root.build for details).
+#
+libue{test-meta}: $libodb
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libs
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_add_c_ \
+ --schema-version-table evo_add_c_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/add-column/driver.cxx b/odb-tests/evolution/add-column/driver.cxx
new file mode 100644
index 0000000..c9a7263
--- /dev/null
+++ b/odb-tests/evolution/add-column/driver.cxx
@@ -0,0 +1,130 @@
+// file : evolution/add-column/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test adding a new column.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_add_c_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ object o (1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+
+ assert (!p->str);
+ assert (p->num == 999);
+
+ // Migration.
+ //
+ p->str = "abc";
+ p->num = 123;
+ db->update (*p);
+
+ t.commit ();
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+
+ assert (p->str && *p->str == "abc");
+ assert (p->num == 123);
+
+ t.commit ();
+ }
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/add-column/model.hxx b/odb-tests/evolution/add-column/model.hxx
new file mode 100644
index 0000000..6ac9160
--- /dev/null
+++ b/odb-tests/evolution/add-column/model.hxx
@@ -0,0 +1,38 @@
+// file : evolution/add-column/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <string>
+
+#include <odb/core.hxx>
+#include <odb/nullable.hxx>
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+#if MODEL_VERSION == 3
+ odb::nullable<std::string> str;
+
+ #pragma db default(999)
+ unsigned long num;
+#endif
+ };
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/add-column/test1.hxx b/odb-tests/evolution/add-column/test1.hxx
new file mode 100644
index 0000000..b0d7fda
--- /dev/null
+++ b/odb-tests/evolution/add-column/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/add-column/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/add-column/test2.hxx b/odb-tests/evolution/add-column/test2.hxx
new file mode 100644
index 0000000..b62530a
--- /dev/null
+++ b/odb-tests/evolution/add-column/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/add-column/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/add-column/test3.hxx b/odb-tests/evolution/add-column/test3.hxx
new file mode 100644
index 0000000..b24dba1
--- /dev/null
+++ b/odb-tests/evolution/add-column/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/add-column/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/add-column/testscript b/odb-tests/evolution/add-column/testscript
new file mode 100644
index 0000000..4741109
--- /dev/null
+++ b/odb-tests/evolution/add-column/testscript
@@ -0,0 +1,58 @@
+# file : evolution/add-column/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/add-foreign-key/buildfile b/odb-tests/evolution/add-foreign-key/buildfile
new file mode 100644
index 0000000..0fe5468
--- /dev/null
+++ b/odb-tests/evolution/add-foreign-key/buildfile
@@ -0,0 +1,64 @@
+# file : evolution/add-foreign-key/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+import libodb_db = libodb-$db%lib{odb-$db}
+import libcommon = lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb and libcommon
+# libraries are resolved for the odb_compile ad hoc rule (see build/root.build
+# for details).
+#
+libue{test-meta}: $libodb $libcommon
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libodb_db
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_add_fk_ \
+ --schema-version-table evo_add_fk_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml \
+ --fkeys-deferrable-mode not_deferrable
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/add-foreign-key/driver.cxx b/odb-tests/evolution/add-foreign-key/driver.cxx
new file mode 100644
index 0000000..735166f
--- /dev/null
+++ b/odb-tests/evolution/add-foreign-key/driver.cxx
@@ -0,0 +1,178 @@
+// file : evolution/add-foreign-key/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test adding a foreign key.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_add_fk_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ object o (1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+#ifdef DATABASE_SQLITE
+ // In SQLite we can only add foreign keys inline in the column
+ // definition.
+ //
+ db->connection ()->execute ("PRAGMA foreign_keys=OFF");
+#endif
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ // Both pointers are now NULL.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+
+ assert (p->o1 == 0 && p->o2 == 0);
+
+ // Migration. The foreign key constraint is not yet there.
+ //
+ p->o1 = new object1 (1);
+ p->o2 = new object2 (1);
+ db->update (*p);
+
+ t.commit ();
+ }
+
+ // Migration. Add the missing objects.
+ //
+ object1 o1 (1);
+ object2 o2 (1);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->o1->id_ == 1);
+ assert (p->o2->id_ == 1);
+ t.commit ();
+ }
+
+ // Now the foreign key constraint is there.
+ //
+ try
+ {
+ object o (2);
+ o.o1 = new object1 (2);
+ o.o2 = new object2 (2);
+
+ transaction t (db->begin ());
+ db->persist (o);
+ assert (false);
+ }
+ catch (const odb::exception& ) {}
+
+ // As well as the NOT NULL.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ object o (3);
+ o.o2 = 0;
+
+ transaction t (db->begin ());
+ db->persist (o);
+ assert (false);
+ }
+ catch (const odb::exception& ) {}
+#endif
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/add-foreign-key/model.hxx b/odb-tests/evolution/add-foreign-key/model.hxx
new file mode 100644
index 0000000..17cd85f
--- /dev/null
+++ b/odb-tests/evolution/add-foreign-key/model.hxx
@@ -0,0 +1,66 @@
+// file : evolution/add-foreign-key/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <string>
+
+#include <odb/core.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+#if MODEL_VERSION == 3
+ #pragma db object
+ struct object1
+ {
+ object1 (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+ };
+
+ #pragma db object
+ struct object2
+ {
+ object2 (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+ };
+#endif
+
+ #pragma db object
+ struct object
+ {
+ #pragma db id
+ unsigned long id_;
+
+#if MODEL_VERSION == 2
+ object (unsigned long id = 0): id_ (id) {}
+#else
+ object1* o1;
+
+ // There is no support for changing a column to NOT NULL in SQLite.
+ //
+#ifndef ODB_DATABASE_SQLITE
+ #pragma db not_null
+#endif
+ object2* o2;
+
+ object (unsigned long id = 0): id_ (id), o1 (0), o2 (0) {}
+ ~object () {delete o1; delete o2;}
+#endif
+ };
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/add-foreign-key/test1.hxx b/odb-tests/evolution/add-foreign-key/test1.hxx
new file mode 100644
index 0000000..05c78c3
--- /dev/null
+++ b/odb-tests/evolution/add-foreign-key/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/add-foreign-key/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/add-foreign-key/test2.hxx b/odb-tests/evolution/add-foreign-key/test2.hxx
new file mode 100644
index 0000000..c57d9a1
--- /dev/null
+++ b/odb-tests/evolution/add-foreign-key/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/add-foreign-key/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/add-foreign-key/test3.hxx b/odb-tests/evolution/add-foreign-key/test3.hxx
new file mode 100644
index 0000000..c844469
--- /dev/null
+++ b/odb-tests/evolution/add-foreign-key/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/add-foreign-key/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/add-foreign-key/testscript b/odb-tests/evolution/add-foreign-key/testscript
new file mode 100644
index 0000000..9ea393f
--- /dev/null
+++ b/odb-tests/evolution/add-foreign-key/testscript
@@ -0,0 +1,58 @@
+# file : evolution/add-foreign-key/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/add-index/buildfile b/odb-tests/evolution/add-index/buildfile
new file mode 100644
index 0000000..9835d1e
--- /dev/null
+++ b/odb-tests/evolution/add-index/buildfile
@@ -0,0 +1,63 @@
+# file : evolution/add-index/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+
+import libs = libodb-$db%lib{odb-$db}
+import libs += lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the odb_compile ad hoc rule (see build/root.build for details).
+#
+libue{test-meta}: $libodb
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libs
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_add_i_ \
+ --schema-version-table evo_add_i_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/add-index/driver.cxx b/odb-tests/evolution/add-index/driver.cxx
new file mode 100644
index 0000000..6b1d15e
--- /dev/null
+++ b/odb-tests/evolution/add-index/driver.cxx
@@ -0,0 +1,170 @@
+// file : evolution/add-index/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test adding a new index.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_add_i_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ object o0 (0);
+ o0.num = 123;
+
+ object o1 (1);
+ o1.num = 234;
+
+ object o2 (2);
+ o2.num = 234;
+
+ // Duplicates are ok.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o0);
+ db->persist (o1);
+ db->persist (o2);
+ t.commit ();
+ }
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ object o3 (3);
+ o3.num = 234;
+
+ // Duplicates are still ok but we need to remove them before the
+ // post migration step.
+ //
+ {
+ transaction t (db->begin ());
+ db->persist (o3);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (
+ "ORDER BY" + query::num + "," + query::id));
+
+ unsigned long prev (0);
+ for (result::iterator i (r.begin ()); i != r.end (); ++i)
+ {
+ if (i->num == prev)
+ db->erase (*i);
+
+ prev = i->num;
+ }
+
+ t.commit ();
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p0 (db->load<object> (0));
+ unique_ptr<object> p1 (db->load<object> (1));
+
+ assert (p0->num == 123);
+ assert (p1->num == 234);
+
+ t.commit ();
+ }
+
+ try
+ {
+ object o2 (2);
+ o2.num = 234;
+
+ transaction t (db->begin ());
+ db->persist (o2);
+ assert (false);
+ }
+ catch (const odb::exception& ) {}
+
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/add-index/model.hxx b/odb-tests/evolution/add-index/model.hxx
new file mode 100644
index 0000000..fec75cc
--- /dev/null
+++ b/odb-tests/evolution/add-index/model.hxx
@@ -0,0 +1,33 @@
+// file : evolution/add-index/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <odb/core.hxx>
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+#if MODEL_VERSION == 3
+ #pragma db unique
+#endif
+ unsigned long num;
+ };
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/add-index/test1.hxx b/odb-tests/evolution/add-index/test1.hxx
new file mode 100644
index 0000000..1be2b5b
--- /dev/null
+++ b/odb-tests/evolution/add-index/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/add-index/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/add-index/test2.hxx b/odb-tests/evolution/add-index/test2.hxx
new file mode 100644
index 0000000..a0faca9
--- /dev/null
+++ b/odb-tests/evolution/add-index/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/add-index/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/add-index/test3.hxx b/odb-tests/evolution/add-index/test3.hxx
new file mode 100644
index 0000000..aab9c86
--- /dev/null
+++ b/odb-tests/evolution/add-index/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/add-index/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/add-index/testscript b/odb-tests/evolution/add-index/testscript
new file mode 100644
index 0000000..e71352a
--- /dev/null
+++ b/odb-tests/evolution/add-index/testscript
@@ -0,0 +1,58 @@
+# file : evolution/add-index/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/add-table/buildfile b/odb-tests/evolution/add-table/buildfile
new file mode 100644
index 0000000..d17cd70
--- /dev/null
+++ b/odb-tests/evolution/add-table/buildfile
@@ -0,0 +1,63 @@
+# file : evolution/add-table/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+
+import libs = libodb-$db%lib{odb-$db}
+import libs += lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the odb_compile ad hoc rule (see build/root.build for details).
+#
+libue{test-meta}: $libodb
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libs
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_add_t_ \
+ --schema-version-table evo_add_t_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/add-table/driver.cxx b/odb-tests/evolution/add-table/driver.cxx
new file mode 100644
index 0000000..1793553
--- /dev/null
+++ b/odb-tests/evolution/add-table/driver.cxx
@@ -0,0 +1,145 @@
+// file : evolution/add-table/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test adding a new table (object, container).
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_add_t_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ // SQLite has broken foreign keys when it comes to DDL.
+ //
+#ifdef DATABASE_SQLITE
+ db->connection ()->execute ("PRAGMA foreign_keys=OFF");
+#endif
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+
+#ifdef DATABASE_SQLITE
+ db->connection ()->execute ("PRAGMA foreign_keys=ON");
+#endif
+ }
+
+ object o (1);
+ o.str = "abc";
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ object1 o1;
+ o1.nums.push_back (1);
+ o1.nums.push_back (2);
+ o1.nums.push_back (3);
+
+ {
+ transaction t (db->begin ());
+ o1.o = db->load<object> (1);
+ db->persist (o1);
+ t.commit ();
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ typedef odb::query<object1> query;
+ typedef odb::result<object1> result;
+
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<object1> (query::o->str == "abc"));
+ result::iterator i (r.begin ()), e (r.end ());
+
+ assert (i != e &&
+ i->o->id_ == 1 &&
+ i->nums[0] == 1 && i->nums[1] == 2 && i->nums[2] == 3);
+ assert (++i == e);
+
+ t.commit ();
+ }
+
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/add-table/model.hxx b/odb-tests/evolution/add-table/model.hxx
new file mode 100644
index 0000000..208a156
--- /dev/null
+++ b/odb-tests/evolution/add-table/model.hxx
@@ -0,0 +1,48 @@
+// file : evolution/add-table/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <vector>
+#include <string>
+
+#include <odb/core.hxx>
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string str;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db object
+ struct object1
+ {
+ object1 (): o (0) {}
+ ~object1 () {delete o;}
+
+ #pragma db id auto
+ unsigned long id_;
+
+ object* o;
+ std::vector<int> nums;
+ };
+#endif
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/add-table/test1.hxx b/odb-tests/evolution/add-table/test1.hxx
new file mode 100644
index 0000000..adc51a1
--- /dev/null
+++ b/odb-tests/evolution/add-table/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/add-table/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/add-table/test2.hxx b/odb-tests/evolution/add-table/test2.hxx
new file mode 100644
index 0000000..ca03cef
--- /dev/null
+++ b/odb-tests/evolution/add-table/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/add-table/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/add-table/test3.hxx b/odb-tests/evolution/add-table/test3.hxx
new file mode 100644
index 0000000..39406a2
--- /dev/null
+++ b/odb-tests/evolution/add-table/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/add-table/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/add-table/testscript b/odb-tests/evolution/add-table/testscript
new file mode 100644
index 0000000..fad1225
--- /dev/null
+++ b/odb-tests/evolution/add-table/testscript
@@ -0,0 +1,58 @@
+# file : evolution/add-table/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/alter-column/buildfile b/odb-tests/evolution/alter-column/buildfile
new file mode 100644
index 0000000..08b0ed7
--- /dev/null
+++ b/odb-tests/evolution/alter-column/buildfile
@@ -0,0 +1,63 @@
+# file : evolution/alter-column/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+import libodb_db = libodb-$db%lib{odb-$db}
+import libcommon = lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb and libcommon
+# libraries are resolved for the odb_compile ad hoc rule (see build/root.build
+# for details).
+#
+libue{test-meta}: $libodb $libcommon
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libodb_db
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_alter_c_ \
+ --schema-version-table evo_alter_c_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/alter-column/driver.cxx b/odb-tests/evolution/alter-column/driver.cxx
new file mode 100644
index 0000000..cf7cbc3
--- /dev/null
+++ b/odb-tests/evolution/alter-column/driver.cxx
@@ -0,0 +1,164 @@
+// file : evolution/alter-column/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test altering a column.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_alter_c_sv"));
+
+ // SQLite doesn't support altering of columns.
+ //
+#ifndef DATABASE_SQLITE
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ object o (1);
+ o.num = 123;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ // NULL is already in effect; NOT NULL is not yet.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+
+ assert (!p->str);
+ assert (p->num && *p->num == 123);
+ assert (!p->num1);
+
+ // Migration.
+ //
+ p->str = "abc";
+ p->num.reset ();
+ p->num1 = 123;
+ db->update (*p);
+
+ t.commit ();
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+
+ assert (p->str && *p->str == "abc");
+ assert (!p->num);
+
+ t.commit ();
+ }
+
+ // NOT NULL is now in effect.
+ //
+ try
+ {
+ object o2 (2);
+ o2.num1 = 234; // str is NULL
+
+ transaction t (db->begin ());
+ db->persist (o2);
+ assert (false);
+ }
+ catch (const odb::exception& ) {}
+
+ try
+ {
+ object o3 (3);
+ o3.str = "bcd"; // num1 is NULL
+
+ transaction t (db->begin ());
+ db->persist (o3);
+ assert (false);
+ }
+ catch (const odb::exception& ) {}
+
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+#endif // DATABASE_SQLITE
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/alter-column/model.hxx b/odb-tests/evolution/alter-column/model.hxx
new file mode 100644
index 0000000..8378887
--- /dev/null
+++ b/odb-tests/evolution/alter-column/model.hxx
@@ -0,0 +1,56 @@
+// file : evolution/alter-column/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <string>
+
+#include <odb/core.hxx>
+#include <odb/nullable.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ // SQLite doesn't support altering of columns.
+ //
+#ifndef DATABASE_SQLITE
+#if MODEL_VERSION == 2
+ odb::nullable<std::string> str;
+
+ unsigned long num;
+#else
+ // Use nullable to be able to access during migration.
+ //
+ #pragma db not_null
+ odb::nullable<std::string> str;
+
+ odb::nullable<unsigned long> num;
+
+ // Test adding NOT NULL column. It should be added NULL in pre
+ // and then converted to NOT NULL in post.
+ //
+ #pragma db not_null
+ odb::nullable<unsigned long> num1;
+#endif
+#endif
+ };
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/alter-column/test1.hxx b/odb-tests/evolution/alter-column/test1.hxx
new file mode 100644
index 0000000..9353558
--- /dev/null
+++ b/odb-tests/evolution/alter-column/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/alter-column/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/alter-column/test2.hxx b/odb-tests/evolution/alter-column/test2.hxx
new file mode 100644
index 0000000..e2dbed3
--- /dev/null
+++ b/odb-tests/evolution/alter-column/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/alter-column/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/alter-column/test3.hxx b/odb-tests/evolution/alter-column/test3.hxx
new file mode 100644
index 0000000..ac08e2f
--- /dev/null
+++ b/odb-tests/evolution/alter-column/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/alter-column/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/alter-column/testscript b/odb-tests/evolution/alter-column/testscript
new file mode 100644
index 0000000..ff8f9a6
--- /dev/null
+++ b/odb-tests/evolution/alter-column/testscript
@@ -0,0 +1,59 @@
+# file : evolution/alter-column/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ # Note that SQLite doesn't support altering of columns and so the driver is
+ # trivial. We, however, still run it once for good measure.
+ #
+ $* 1
+}
diff --git a/odb-tests/evolution/combined/buildfile b/odb-tests/evolution/combined/buildfile
new file mode 100644
index 0000000..0ccce68
--- /dev/null
+++ b/odb-tests/evolution/combined/buildfile
@@ -0,0 +1,64 @@
+# file : evolution/combined/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+import libodb_db = libodb-$db%lib{odb-$db}
+import libcommon = lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb and libcommon
+# libraries are resolved for the odb_compile ad hoc rule (see build/root.build
+# for details).
+#
+libue{test-meta}: $libodb $libcommon
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libodb_db
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_comb_ \
+ --schema-version-table evo_comb_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml \
+ --sqlite-override-null
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/combined/driver.cxx b/odb-tests/evolution/combined/driver.cxx
new file mode 100644
index 0000000..2b77437
--- /dev/null
+++ b/odb-tests/evolution/combined/driver.cxx
@@ -0,0 +1,162 @@
+// file : evolution/combined/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Combined schema evolution test.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_comb_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ object o ("1");
+ o.dui = 1;
+ o.anui = 1;
+ o.dnui = 1;
+ o.dc = 1;
+ o.dt.push_back (1);
+ o.aui = 1;
+
+#ifndef DATABASE_SQLITE
+ o.dfk = new object1 (1);
+ o.acn = 1;
+ o.acnn.reset ();
+ o.afk = 1;
+#endif
+
+ {
+ transaction t (db->begin ());
+#ifndef DATABASE_SQLITE
+ db->persist (o.dfk);
+#endif
+ db->persist (o);
+ t.commit ();
+ }
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> ("1"));
+
+ assert (p->ac1 == 999);
+ assert (!p->ac2);
+
+#ifndef DATABASE_SQLITE
+ assert (!p->ac3);
+#endif
+ // Migrate.
+ //
+ p->at.push_back ("abc");
+
+#ifndef DATABASE_SQLITE
+ p->dfk = 999;
+ p->ac3 = 1;
+ p->acn.reset ();
+ p->acnn = 1;
+#endif
+ db->update (*p);
+
+ t.commit ();
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> ("1"));
+
+ // Check post-migration.
+ //
+ assert (p->at[0] == "abc");
+
+#ifndef DATABASE_SQLITE
+ assert (p->dfk == 999);
+ assert (*p->ac3 == 1);
+ assert (!p->acn);
+ assert (*p->acnn == 1);
+#endif
+ t.commit ();
+ }
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/combined/model.hxx b/odb-tests/evolution/combined/model.hxx
new file mode 100644
index 0000000..5f1180b
--- /dev/null
+++ b/odb-tests/evolution/combined/model.hxx
@@ -0,0 +1,174 @@
+// file : evolution/combined/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <string>
+#include <vector>
+
+#include <odb/core.hxx>
+#include <odb/nullable.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+ #pragma db object
+ struct object1
+ {
+ object1 (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+ };
+
+ #pragma db object
+ struct object
+ {
+ #pragma db id
+ std::string id_;
+
+ //
+ // Pre pass 1.
+ //
+
+ // Drop unique index.
+ //
+#if MODEL_VERSION == 2
+ #pragma db unique
+#endif
+ unsigned long dui;
+
+ // Alter table drop foreign key. Not supported by SQLite.
+ //
+#ifndef DATABASE_SQLITE
+#if MODEL_VERSION == 2
+ object1* dfk;
+#else
+ #pragma db null
+ unsigned long dfk;
+#endif
+#endif
+
+ // Add table.
+ //
+#if MODEL_VERSION == 3
+ std::vector<std::string> at;
+#endif
+
+ // Add column.
+ //
+#if MODEL_VERSION == 3
+ #pragma db default(999)
+ unsigned long ac1;
+
+ odb::nullable<unsigned long> ac2;
+
+ // Initially added as NULL, converted to NOT NULL. Not supported by SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ #pragma db not_null
+ odb::nullable<unsigned long> ac3;
+#endif
+#endif
+
+ // Alter column NULL. Not supported by SQLite.
+ //
+#ifndef DATABASE_SQLITE
+#if MODEL_VERSION == 2
+ unsigned long acn;
+#else
+ odb::nullable<unsigned long> acn;
+#endif
+#endif
+
+ //
+ // Pre pass 2.
+ //
+
+ // Add non-unique indexes.
+ //
+#if MODEL_VERSION == 3
+ #pragma db index
+#endif
+ unsigned long anui;
+
+ //
+ // Post pass 1.
+ //
+
+ // Drop non-unique indexes.
+ //
+#if MODEL_VERSION == 2
+ #pragma db index
+#endif
+ unsigned long dnui;
+
+ //
+ // Post pass 2.
+ //
+
+ // Drop table.
+ //
+#if MODEL_VERSION == 2
+ std::vector<unsigned long> dt;
+#endif
+
+ // Drop column. Logical drop (set NULL) in SQLite.
+ //
+#if MODEL_VERSION == 2
+ unsigned long dc;
+#endif
+
+ // Alter column NOT NULL. Not supported by SQLite.
+ //
+#ifndef DATABASE_SQLITE
+#if MODEL_VERSION == 3
+ #pragma db not_null
+#endif
+ odb::nullable<unsigned long> acnn;
+#endif
+
+ // Alter table add foreign key. Not supported by SQLite.
+ //
+#ifndef DATABASE_SQLITE
+#if MODEL_VERSION == 2
+ #pragma db null
+ unsigned long afk;
+#else
+ object1* afk;
+#endif
+#endif
+
+ // Add unique index.
+ //
+#if MODEL_VERSION == 3
+ #pragma db unique
+#endif
+ unsigned long aui;
+
+ public:
+#ifndef DATABASE_SQLITE
+#if MODEL_VERSION == 2
+
+ object (std::string id = ""): id_ (id), dfk (0) {}
+ ~object () {delete dfk;}
+#else
+ object (std::string id = ""): id_ (id), afk (0) {}
+ ~object () {delete afk;}
+#endif
+#else
+ object (std::string id = ""): id_ (id) {}
+#endif
+ };
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/combined/test1.hxx b/odb-tests/evolution/combined/test1.hxx
new file mode 100644
index 0000000..1a18aff
--- /dev/null
+++ b/odb-tests/evolution/combined/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/combined/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/combined/test2.hxx b/odb-tests/evolution/combined/test2.hxx
new file mode 100644
index 0000000..8eb7ac8
--- /dev/null
+++ b/odb-tests/evolution/combined/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/combined/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/combined/test3.hxx b/odb-tests/evolution/combined/test3.hxx
new file mode 100644
index 0000000..05a052f
--- /dev/null
+++ b/odb-tests/evolution/combined/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/combined/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/combined/testscript b/odb-tests/evolution/combined/testscript
new file mode 100644
index 0000000..118e43b
--- /dev/null
+++ b/odb-tests/evolution/combined/testscript
@@ -0,0 +1,58 @@
+# file : evolution/combined/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/data/buildfile b/odb-tests/evolution/data/buildfile
new file mode 100644
index 0000000..d013cab
--- /dev/null
+++ b/odb-tests/evolution/data/buildfile
@@ -0,0 +1,63 @@
+# file : evolution/data/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+
+import libs = libodb-$db%lib{odb-$db}
+import libs += lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the odb_compile ad hoc rule (see build/root.build for details).
+#
+libue{test-meta}: $libodb
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libs
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_data_ \
+ --schema-version-table evo_data_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/data/driver.cxx b/odb-tests/evolution/data/driver.cxx
new file mode 100644
index 0000000..685f556
--- /dev/null
+++ b/odb-tests/evolution/data/driver.cxx
@@ -0,0 +1,179 @@
+// file : evolution/data/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test data migration support.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+void
+migrate1 (database& db)
+{
+ using namespace v2;
+ using namespace v3;
+
+ unique_ptr<object1> o1 (db.load<object1> (1));
+ object2 o2 (1);
+ o2.num = o1->num;
+ db.persist (o2);
+}
+
+void
+migrate2 (database& db)
+{
+ using namespace v2;
+ using namespace v3;
+
+ unique_ptr<object1> o1 (db.load<object1> (2));
+ object2 o2 (2);
+ o2.num = o1->num;
+ db.persist (o2);
+}
+
+static const data_migration_entry<3, 1> migrate2_entry (&migrate2);
+
+int
+main (int argc, char* argv[])
+{
+ schema_catalog::data_migration_function<3, 1> (&migrate1);
+
+ schema_catalog::data_migration_function<3, 1> (
+ [] (database& db)
+ {
+ using namespace v2;
+ using namespace v3;
+
+ unique_ptr<object1> o1 (db.load<object1> (11));
+ object2 o2 (11);
+ o2.num = o1->num;
+ db.persist (o2);
+ });
+
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_data_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+
+ {
+ object1 o1 (1);
+ o1.num = 123;
+ db->persist (o1);
+ }
+
+ {
+ object1 o1 (2);
+ o1.num = 123;
+ db->persist (o1);
+ }
+
+ {
+ object1 o1 (11);
+ o1.num = 123;
+ db->persist (o1);
+ }
+
+ t.commit ();
+ }
+ break;
+ }
+ case 2:
+ {
+ {
+ transaction t (db->begin ());
+
+ if (embedded)
+ schema_catalog::migrate (*db);
+ else
+ schema_catalog::migrate_data (*db);
+
+ t.commit ();
+ }
+
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ {
+ transaction t (db->begin ());
+
+ {
+ unique_ptr<object2> o2 (db->load<object2> (1));
+ assert (o2->num == 123);
+ }
+
+ {
+ unique_ptr<object2> o2 (db->load<object2> (2));
+ assert (o2->num == 123);
+ }
+
+ {
+ unique_ptr<object2> o2 (db->load<object2> (11));
+ assert (o2->num == 123);
+ }
+
+ t.commit ();
+ }
+
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/data/model.hxx b/odb-tests/evolution/data/model.hxx
new file mode 100644
index 0000000..680bc55
--- /dev/null
+++ b/odb-tests/evolution/data/model.hxx
@@ -0,0 +1,45 @@
+// file : evolution/data/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <odb/core.hxx>
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+#if MODEL_VERSION == 2
+ #pragma db object
+ struct object1
+ {
+ object1 (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ int num;
+ };
+#endif
+
+#if MODEL_VERSION == 3
+ #pragma db object
+ struct object2
+ {
+ object2 (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ int num;
+ };
+#endif
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/data/test1.hxx b/odb-tests/evolution/data/test1.hxx
new file mode 100644
index 0000000..976ed04
--- /dev/null
+++ b/odb-tests/evolution/data/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/data/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/data/test2.hxx b/odb-tests/evolution/data/test2.hxx
new file mode 100644
index 0000000..bd72940
--- /dev/null
+++ b/odb-tests/evolution/data/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/data/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/data/test3.hxx b/odb-tests/evolution/data/test3.hxx
new file mode 100644
index 0000000..0f47099
--- /dev/null
+++ b/odb-tests/evolution/data/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/data/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/data/testscript b/odb-tests/evolution/data/testscript
new file mode 100644
index 0000000..dddc909
--- /dev/null
+++ b/odb-tests/evolution/data/testscript
@@ -0,0 +1,58 @@
+# file : evolution/data/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/drop-column/buildfile b/odb-tests/evolution/drop-column/buildfile
new file mode 100644
index 0000000..c508f76
--- /dev/null
+++ b/odb-tests/evolution/drop-column/buildfile
@@ -0,0 +1,64 @@
+# file : evolution/drop-column/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+
+import libs = libodb-$db%lib{odb-$db}
+import libs += lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the odb_compile ad hoc rule (see build/root.build for details).
+#
+libue{test-meta}: $libodb
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libs
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_drop_c_ \
+ --schema-version-table evo_drop_c_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml \
+ --sqlite-override-null
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/drop-column/driver.cxx b/odb-tests/evolution/drop-column/driver.cxx
new file mode 100644
index 0000000..6f3008b
--- /dev/null
+++ b/odb-tests/evolution/drop-column/driver.cxx
@@ -0,0 +1,131 @@
+// file : evolution/drop-column/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test dropping a column.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_drop_c_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ object o (1);
+ o.str = "abc";
+ o.num = 123;
+ o.ptr = new object1 (1, 2);
+
+ {
+ transaction t (db->begin ());
+ db->persist (*o.ptr);
+ db->persist (o);
+ t.commit ();
+ }
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ // Things are still there.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+
+ assert (p->str == "abc");
+ assert (p->num == 123);
+ assert (p->ptr->id.x == 1 && p->ptr->id.y == 2);
+
+ t.commit ();
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ // Now they are gone.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "" && p->ptr == 0);
+ db->erase<object1> (value (1, 2)); // SQLite logical delete test.
+ t.commit ();
+ }
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/drop-column/model.hxx b/odb-tests/evolution/drop-column/model.hxx
new file mode 100644
index 0000000..09b63b9
--- /dev/null
+++ b/odb-tests/evolution/drop-column/model.hxx
@@ -0,0 +1,59 @@
+// file : evolution/drop-column/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <string>
+
+#include <odb/core.hxx>
+#include <odb/nullable.hxx>
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+ #pragma db value
+ struct value
+ {
+ value (int x_ = 0, int y_ = 0): x (x_), y (y_) {}
+ int x;
+ int y;
+ };
+
+ #pragma db object
+ struct object1
+ {
+ object1 (int x = 0, int y = 0): id (x, y) {}
+
+ #pragma db id
+ value id;
+ };
+
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id), ptr (0) {}
+ ~object () {delete ptr;}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string str;
+ unsigned long num;
+ object1* ptr;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) deleted(3)
+ #pragma db member(object::num) deleted(3)
+ #pragma db member(object::ptr) deleted(3)
+#endif
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/drop-column/test1.hxx b/odb-tests/evolution/drop-column/test1.hxx
new file mode 100644
index 0000000..1efb1fa
--- /dev/null
+++ b/odb-tests/evolution/drop-column/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/drop-column/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/drop-column/test2.hxx b/odb-tests/evolution/drop-column/test2.hxx
new file mode 100644
index 0000000..2842cd3
--- /dev/null
+++ b/odb-tests/evolution/drop-column/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/drop-column/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/drop-column/test3.hxx b/odb-tests/evolution/drop-column/test3.hxx
new file mode 100644
index 0000000..d1ce616
--- /dev/null
+++ b/odb-tests/evolution/drop-column/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/drop-column/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/drop-column/testscript b/odb-tests/evolution/drop-column/testscript
new file mode 100644
index 0000000..bfda778
--- /dev/null
+++ b/odb-tests/evolution/drop-column/testscript
@@ -0,0 +1,58 @@
+# file : evolution/drop-column/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/drop-foreign-key/buildfile b/odb-tests/evolution/drop-foreign-key/buildfile
new file mode 100644
index 0000000..6d1441e
--- /dev/null
+++ b/odb-tests/evolution/drop-foreign-key/buildfile
@@ -0,0 +1,64 @@
+# file : evolution/drop-foreign-key/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+import libodb_db = libodb-$db%lib{odb-$db}
+import libcommon = lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb and libcommon
+# libraries are resolved for the odb_compile ad hoc rule (see build/root.build
+# for details).
+#
+libue{test-meta}: $libodb $libcommon
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libodb_db
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_drop_fk_ \
+ --schema-version-table evo_drop_fk_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml \
+ --fkeys-deferrable-mode not_deferrable
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/drop-foreign-key/driver.cxx b/odb-tests/evolution/drop-foreign-key/driver.cxx
new file mode 100644
index 0000000..5556cce
--- /dev/null
+++ b/odb-tests/evolution/drop-foreign-key/driver.cxx
@@ -0,0 +1,149 @@
+// file : evolution/drop-foreign-key/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test dropping a foreign key.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_drop_fk_sv"));
+
+ // SQLite doesn't support dropping of foreign keys.
+ //
+#ifndef DATABASE_SQLITE
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ object o (1);
+ o.o1 = new object (2);
+ o.o2 = new object (3);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o.o1);
+ db->persist (o.o2);
+ db->persist (o);
+ t.commit ();
+ }
+
+ // The foreign key constraint is there.
+ //
+ try
+ {
+ object o (11);
+ o.o1 = new object (12);
+ o.o2 = new object (13);
+
+ transaction t (db->begin ());
+ db->persist (o);
+ assert (false);
+ }
+ catch (const odb::exception& ) {}
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ // The data is still there but the constraints are gone.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+
+ assert (p->o1 == 2);
+ assert (p->o2 == 3);
+
+ db->erase<object> (p->o1);
+ db->erase<object> (p->o2);
+
+ t.commit ();
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->o1 == 2);
+ assert (p->o2 == 3);
+ t.commit ();
+ }
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+#endif // DATABASE_SQLITE
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/drop-foreign-key/model.hxx b/odb-tests/evolution/drop-foreign-key/model.hxx
new file mode 100644
index 0000000..a74d061
--- /dev/null
+++ b/odb-tests/evolution/drop-foreign-key/model.hxx
@@ -0,0 +1,50 @@
+// file : evolution/drop-foreign-key/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <string>
+
+#include <odb/core.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+ #pragma db object
+ struct object
+ {
+ #pragma db id
+ unsigned long id_;
+
+ // SQLite doesn't support dropping of foreign keys.
+ //
+#ifndef DATABASE_SQLITE
+#if MODEL_VERSION == 2
+ object* o1;
+ object* o2;
+
+ object (unsigned long id = 0): id_ (id), o1 (0), o2 (0) {}
+ ~object () {delete o1; delete o2;}
+#else
+ #pragma db null
+ unsigned long o1;
+
+ #pragma db null
+ unsigned long o2;
+
+ object (unsigned long id = 0): id_ (id) {}
+#endif
+#endif
+ };
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/drop-foreign-key/test1.hxx b/odb-tests/evolution/drop-foreign-key/test1.hxx
new file mode 100644
index 0000000..5476796
--- /dev/null
+++ b/odb-tests/evolution/drop-foreign-key/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/drop-foreign-key/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/drop-foreign-key/test2.hxx b/odb-tests/evolution/drop-foreign-key/test2.hxx
new file mode 100644
index 0000000..f091a25
--- /dev/null
+++ b/odb-tests/evolution/drop-foreign-key/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/drop-foreign-key/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/drop-foreign-key/test3.hxx b/odb-tests/evolution/drop-foreign-key/test3.hxx
new file mode 100644
index 0000000..beb8c42
--- /dev/null
+++ b/odb-tests/evolution/drop-foreign-key/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/drop-foreign-key/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/drop-foreign-key/testscript b/odb-tests/evolution/drop-foreign-key/testscript
new file mode 100644
index 0000000..f48e801
--- /dev/null
+++ b/odb-tests/evolution/drop-foreign-key/testscript
@@ -0,0 +1,59 @@
+# file : evolution/drop-foreign-key/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ # Note that SQLite doesn't support dropping of foreign keys and so the
+ # driver is trivial. We, however, still run it once for good measure.
+ #
+ $* 1
+}
diff --git a/odb-tests/evolution/drop-index/buildfile b/odb-tests/evolution/drop-index/buildfile
new file mode 100644
index 0000000..16cf57e
--- /dev/null
+++ b/odb-tests/evolution/drop-index/buildfile
@@ -0,0 +1,63 @@
+# file : evolution/drop-index/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+
+import libs = libodb-$db%lib{odb-$db}
+import libs += lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the odb_compile ad hoc rule (see build/root.build for details).
+#
+libue{test-meta}: $libodb
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libs
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_drop_i_ \
+ --schema-version-table evo_drop_i_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/drop-index/driver.cxx b/odb-tests/evolution/drop-index/driver.cxx
new file mode 100644
index 0000000..2b06ca7
--- /dev/null
+++ b/odb-tests/evolution/drop-index/driver.cxx
@@ -0,0 +1,159 @@
+// file : evolution/drop-index/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test dropping an index.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_drop_i_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ object o0 (0);
+ o0.num = 123;
+
+ object o1 (1);
+ o1.num = 234;
+
+ object o2 (2);
+ o2.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o0);
+ db->persist (o1);
+ t.commit ();
+ }
+
+ // Duplicates are not ok.
+ //
+ try
+ {
+ transaction t (db->begin ());
+ db->persist (o2);
+ assert (false);
+ }
+ catch (const odb::exception& ) {}
+
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ // Duplicates are now ok.
+ //
+ object o2 (2);
+ o2.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o2);
+ t.commit ();
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p0 (db->load<object> (0));
+ unique_ptr<object> p1 (db->load<object> (1));
+ unique_ptr<object> p2 (db->load<object> (2));
+
+ assert (p0->num == 123);
+ assert (p1->num == 234);
+ assert (p2->num == 234);
+
+ t.commit ();
+ }
+
+ // Duplicates are still ok.
+ //
+ object o3 (3);
+ o3.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o3);
+ t.commit ();
+ }
+
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/drop-index/model.hxx b/odb-tests/evolution/drop-index/model.hxx
new file mode 100644
index 0000000..5e163ca
--- /dev/null
+++ b/odb-tests/evolution/drop-index/model.hxx
@@ -0,0 +1,33 @@
+// file : evolution/drop-index/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <odb/core.hxx>
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+#if MODEL_VERSION == 2
+ #pragma db unique
+#endif
+ unsigned long num;
+ };
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/drop-index/test1.hxx b/odb-tests/evolution/drop-index/test1.hxx
new file mode 100644
index 0000000..5a279bb
--- /dev/null
+++ b/odb-tests/evolution/drop-index/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/drop-index/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/drop-index/test2.hxx b/odb-tests/evolution/drop-index/test2.hxx
new file mode 100644
index 0000000..78b89f2
--- /dev/null
+++ b/odb-tests/evolution/drop-index/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/drop-index/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/drop-index/test3.hxx b/odb-tests/evolution/drop-index/test3.hxx
new file mode 100644
index 0000000..17b670d
--- /dev/null
+++ b/odb-tests/evolution/drop-index/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/drop-index/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/drop-index/testscript b/odb-tests/evolution/drop-index/testscript
new file mode 100644
index 0000000..0fb21ba
--- /dev/null
+++ b/odb-tests/evolution/drop-index/testscript
@@ -0,0 +1,58 @@
+# file : evolution/drop-index/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/drop-table/buildfile b/odb-tests/evolution/drop-table/buildfile
new file mode 100644
index 0000000..373ae9f
--- /dev/null
+++ b/odb-tests/evolution/drop-table/buildfile
@@ -0,0 +1,63 @@
+# file : evolution/drop-table/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+
+import libs = libodb-$db%lib{odb-$db}
+import libs += lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the odb_compile ad hoc rule (see build/root.build for details).
+#
+libue{test-meta}: $libodb
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libs
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_drop_t_ \
+ --schema-version-table evo_drop_t_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/drop-table/driver.cxx b/odb-tests/evolution/drop-table/driver.cxx
new file mode 100644
index 0000000..74407da
--- /dev/null
+++ b/odb-tests/evolution/drop-table/driver.cxx
@@ -0,0 +1,173 @@
+// file : evolution/drop-table/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test dropping a table (object, container).
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_drop_t_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ {
+ using namespace v2;
+
+ object1 o1;
+ o1.o = new object (1);
+ o1.o->str = "abc";
+ o1.nums.push_back (1);
+ o1.nums.push_back (2);
+ o1.nums.push_back (3);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o1.o);
+ db->persist (o1);
+ t.commit ();
+ }
+ }
+
+ // Polymorphism test.
+ //
+ {
+ // We have to use v3 here because the discriminator includes
+ // the namespace.
+ //
+ using namespace v3;
+
+ base b (123, "abc");
+ derived d1 (234, "bcd");
+ derived d2 (345, "cde");
+
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ db->persist (d1);
+ db->persist (d2);
+ t.commit ();
+ }
+ }
+
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ // Both object and object1 are still there so we can migrate the data.
+ //
+ typedef odb::query<object1> query;
+ typedef odb::result<object1> result;
+
+ {
+ transaction t (db->begin ());
+
+ result r (db->query<object1> (query::o->str == "abc"));
+ result::iterator i (r.begin ()), e (r.end ());
+
+ assert (i != e &&
+ i->o->id_ == 1 &&
+ i->nums[0] == 1 && i->nums[1] == 2 && i->nums[2] == 3);
+ assert (++i == e);
+
+ t.commit ();
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ // Only object is still there.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "abc");
+ t.commit ();
+ }
+
+ // Polymorphism test.
+ //
+ {
+ transaction t (db->begin ());
+ assert (size (db->query<root> ()) == 1);
+ assert (size (db->query<base> ()) == 1);
+ t.commit ();
+ }
+
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/drop-table/model.hxx b/odb-tests/evolution/drop-table/model.hxx
new file mode 100644
index 0000000..2c02d09
--- /dev/null
+++ b/odb-tests/evolution/drop-table/model.hxx
@@ -0,0 +1,94 @@
+// file : evolution/drop-table/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <vector>
+#include <string>
+
+#include <odb/core.hxx>
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string str;
+ };
+
+ #pragma db object
+ struct object1
+ {
+ object1 (): o (0) {}
+ ~object1 () {delete o;}
+
+ #pragma db id auto
+ unsigned long id_;
+
+ object* o;
+ std::vector<int> nums;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db object(object1) deleted(3)
+#endif
+
+ // Make sure we also clean up base tables when dropping a
+ // table corresponding to the polymorphic derived object.
+ //
+ #pragma db value
+ struct value
+ {
+ value (unsigned long n = 0, const std::string& s = ""): num (n), str (s) {}
+
+ unsigned long num;
+ std::string str;
+ };
+
+ #pragma db object polymorphic
+ struct root
+ {
+ root (unsigned long n = 0, const std::string& s = ""): id (n, s) {}
+ virtual ~root () {}
+
+ #pragma db id
+ value id;
+ };
+
+ #pragma db object
+ struct base: root
+ {
+ base (unsigned long n = 0, const std::string& s = "")
+ : root (n, s), num (n) {}
+
+ unsigned long num;
+ };
+
+ #pragma db object
+ struct derived: base
+ {
+ derived (unsigned long n = 0, const std::string& s = "")
+ : base (n, s), str (s) {}
+
+ std::string str;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db object(derived) deleted(3)
+#endif
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/drop-table/test1.hxx b/odb-tests/evolution/drop-table/test1.hxx
new file mode 100644
index 0000000..a9ec64c
--- /dev/null
+++ b/odb-tests/evolution/drop-table/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/drop-table/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/drop-table/test2.hxx b/odb-tests/evolution/drop-table/test2.hxx
new file mode 100644
index 0000000..c9ec149
--- /dev/null
+++ b/odb-tests/evolution/drop-table/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/drop-table/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/drop-table/test3.hxx b/odb-tests/evolution/drop-table/test3.hxx
new file mode 100644
index 0000000..8d3f17b
--- /dev/null
+++ b/odb-tests/evolution/drop-table/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/drop-table/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/drop-table/testscript b/odb-tests/evolution/drop-table/testscript
new file mode 100644
index 0000000..6263168
--- /dev/null
+++ b/odb-tests/evolution/drop-table/testscript
@@ -0,0 +1,58 @@
+# file : evolution/drop-table/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/embedded/buildfile b/odb-tests/evolution/embedded/buildfile
new file mode 100644
index 0000000..095e476
--- /dev/null
+++ b/odb-tests/evolution/embedded/buildfile
@@ -0,0 +1,59 @@
+# file : evolution/embedded/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+
+import libs = libodb-$db%lib{odb-$db}
+import libs += lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the odb_compile ad hoc rule (see build/root.build for details).
+#
+libue{test-meta}: $libodb
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libs
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_embedded_ \
+ --schema-version-table evo_embedded_sv \
+ --generate-schema \
+ --schema-format embedded \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+<{hxx ixx cxx}{test3-odb}>: odb_options += --omit-create
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/embedded/driver.cxx b/odb-tests/evolution/embedded/driver.cxx
new file mode 100644
index 0000000..2bdb2d1
--- /dev/null
+++ b/odb-tests/evolution/embedded/driver.cxx
@@ -0,0 +1,185 @@
+// file : evolution/embedded/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test embedded schema migration.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+#include <libcommon/common.hxx>
+
+#ifdef DATABASE_PGSQL
+# include <odb/pgsql/connection.hxx>
+#endif
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_embedded_sv"));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ t.commit ();
+ }
+
+ // PostgreSQL cannot continue a transaction after a query failed. We
+ // have a workaround but only for 9.4+.
+ //
+#ifdef DATABASE_PGSQL
+ {
+ odb::connection_ptr c (db->connection ());
+ int v (static_cast<odb::pgsql::connection&> (*c).server_version ());
+ if (v < 90400)
+ assert (db->schema_version () == 0);
+ }
+#endif
+
+ {
+ transaction t (db->begin ());
+ assert (db->schema_version () == 0);
+
+ schema_catalog::create_schema (*db, "", false);
+
+ assert (db->schema_version () == 1 && !db->schema_migration ());
+
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ assert (db->schema_version () == 2 && !db->schema_migration ());
+
+ {
+ transaction t (db->begin ());
+ object1 o1 (1);
+ o1.num = 123;
+ db->persist (o1);
+ t.commit ();
+ }
+ break;
+ }
+ case 2:
+ {
+ using namespace v2;
+ using namespace v3;
+
+ // Check version information correctness.
+ //
+ assert (schema_catalog::base_version (*db) == 1);
+ assert (schema_catalog::current_version (*db) == 3);
+ assert (schema_catalog::next_version (*db, 0) == 3);
+ assert (schema_catalog::next_version (*db, 1) == 2);
+ assert (schema_catalog::next_version (*db) == 3);
+ assert (schema_catalog::next_version (*db, 3) == 4);
+
+ {
+ assert (db->schema_version () == 2 && !db->schema_migration ());
+
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ assert (db->schema_version () == 3 && db->schema_migration ());
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object1> o1 (db->load<object1> (1));
+ object2 o2 (1);
+ o2.num = o1->num;
+ db->persist (o2);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+
+ assert (db->schema_version () == 3 && !db->schema_migration ());
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ // In transaction.
+ //
+ {
+ transaction t (db->begin ());
+ assert (db->schema_version () == 3 && !db->schema_migration ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object2> o2 (db->load<object2> (1));
+ assert (o2->num == 123);
+ t.commit ();
+ }
+
+ // Test the case where there is still no version table.
+ //
+ db->schema_version_migration (0, false);
+
+ {
+ transaction t (db->begin ());
+
+#ifdef DATABASE_ORACLE
+ db->execute ("DROP TABLE \"evo_embedded_sv\"");
+#else
+ db->execute ("DROP TABLE evo_embedded_sv");
+#endif
+ t.commit ();
+ }
+
+ assert (db->schema_version () == 0);
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/embedded/model.hxx b/odb-tests/evolution/embedded/model.hxx
new file mode 100644
index 0000000..f3aa7a4
--- /dev/null
+++ b/odb-tests/evolution/embedded/model.hxx
@@ -0,0 +1,45 @@
+// file : evolution/embedded/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <odb/core.hxx>
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+#if MODEL_VERSION == 2
+ #pragma db object
+ struct object1
+ {
+ object1 (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ int num;
+ };
+#endif
+
+#if MODEL_VERSION == 3
+ #pragma db object
+ struct object2
+ {
+ object2 (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ int num;
+ };
+#endif
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/embedded/test1.hxx b/odb-tests/evolution/embedded/test1.hxx
new file mode 100644
index 0000000..32903a1
--- /dev/null
+++ b/odb-tests/evolution/embedded/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/embedded/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/embedded/test2.hxx b/odb-tests/evolution/embedded/test2.hxx
new file mode 100644
index 0000000..fce8760
--- /dev/null
+++ b/odb-tests/evolution/embedded/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/embedded/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/embedded/test3.hxx b/odb-tests/evolution/embedded/test3.hxx
new file mode 100644
index 0000000..d49ecc5
--- /dev/null
+++ b/odb-tests/evolution/embedded/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/embedded/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/embedded/testscript b/odb-tests/evolution/embedded/testscript
new file mode 100644
index 0000000..3ddf983
--- /dev/null
+++ b/odb-tests/evolution/embedded/testscript
@@ -0,0 +1,21 @@
+# file : evolution/embedded/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ $* 1;
+ $* 2;
+ $* 3
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/soft-add/buildfile b/odb-tests/evolution/soft-add/buildfile
new file mode 100644
index 0000000..6a61697
--- /dev/null
+++ b/odb-tests/evolution/soft-add/buildfile
@@ -0,0 +1,64 @@
+# file : evolution/soft-add/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+
+import libs = libodb-$db%lib{odb-$db}
+import libs += lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the odb_compile ad hoc rule (see build/root.build for details).
+#
+libue{test-meta}: $libodb
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libs
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_soft_a_ \
+ --schema-version-table evo_soft_a_sv \
+ --generate-schema \
+ --generate-query \
+ --generate-prepared \
+ --at-once \
+ --changelog $out_base/model.xml
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/soft-add/driver.cxx b/odb-tests/evolution/soft-add/driver.cxx
new file mode 100644
index 0000000..3bd5f1f
--- /dev/null
+++ b/odb-tests/evolution/soft-add/driver.cxx
@@ -0,0 +1,2224 @@
+// file : evolution/soft-add/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test soft-add functionality.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_soft_a_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v3; // NOTE: not v2.
+
+ if (embedded)
+ {
+ // SQLite has broken foreign keys when it comes to DDL.
+ //
+#ifdef DATABASE_SQLITE
+ db->connection ()->execute ("PRAGMA foreign_keys=OFF");
+#endif
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+
+#ifdef DATABASE_SQLITE
+ db->connection ()->execute ("PRAGMA foreign_keys=ON");
+#endif
+ }
+
+ // Test basic soft-added member logic.
+ //
+ {
+ using namespace test2;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o (1);
+ o.str = "abc";
+ o.num = 123;
+ o.vec.push_back (123);
+ o.ptr = new object1 (1);
+ o.ptr->ptrs.push_back (&o);
+
+ transaction t (db->begin ());
+ db->persist (o);
+ db->persist (*o.ptr);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "" && p->num == 123 &&
+ p->vec.empty () && p->ptr == 0);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () &&
+ i->str == "" && i->num == 123 &&
+ i->vec.empty () && i->ptr == 0);
+
+ try
+ {
+ db->query<object> (query::str.is_null ()); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+ o.ptr = new object1 (2);
+ o.ptr->ptrs.push_back (&o);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ db->persist (*o.ptr);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "" && p->num == 234 &&
+ p->vec.empty () && p->ptr == 0);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++;
+ delete o.ptr;
+ o.ptr = 0;
+
+ {
+ transaction t (db->begin ());
+ db->erase<object1> (2);
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "" && p->num == 235 &&
+ p->vec.empty () && p->ptr == 0);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test empty statement handling (INSERT, UPDATE).
+ //
+ {
+ using namespace test3;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ object o;
+ o.str = "bcd";
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "");
+ t.commit ();
+ }
+
+ o.str += 'e';
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "");
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test empty statement handling (SELECT in polymorphic loader).
+ //
+ {
+ using namespace test4;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ object o (1);
+ o.str = "abc";
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<base> p (db->load<base> (1));
+ assert (static_cast<object&> (*p).str == "");
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test container with soft-added value member.
+ //
+ {
+ using namespace test5;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o (1);
+ o.vec.push_back (value ("abc", 123));
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->vec[0].str == "" && p->vec[0].num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.vec.push_back (value ("bcd", 234));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->vec[0].str == "" && p->vec[0].num == 234);
+ t.commit ();
+ }
+
+ o.vec.modify (0).str += 'e';
+ o.vec.modify (0).num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->vec[0].str == "" && p->vec[0].num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test view with soft-added member.
+ //
+ {
+ using namespace test6;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o (1);
+ o.str = "abc";
+ o.num = 123;
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<view> query;
+ typedef odb::result<view> result;
+
+ transaction t (db->begin ());
+ result r (db->query<view> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "" && i->num == 123);
+
+ try
+ {
+ db->query<object> (query::str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+ }
+
+ // Test soft-added section member.
+ //
+ {
+ using namespace test7;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o (1);
+ o.str = "abc";
+ o.num = 123;
+ o.vec.push_back (123);
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+
+ try
+ {
+ db->load (*p, p->s); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++;
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added members of a section.
+ //
+ {
+ using namespace test8;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o (1);
+ o.str = "abc";
+ o.num = 123;
+ o.vec.push_back (123);
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->str == "" && p->num == 123 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ assert (i->str == "" && i->num == 123 && i->vec.empty ());
+
+ try
+ {
+ db->query<object> (query::str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "" && p->num == 234 && p->vec.empty ());
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++; // No longer automatically marked as changed.
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "" && p->num == 234 && p->vec.empty ());
+ t.commit ();
+ }
+
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "" && p->num == 235 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test basic soft-added member logic in polymorphic classes.
+ //
+ {
+ using namespace test9;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o (1);
+ o.bstr = "ab";
+ o.dstr = "abc";
+ o.num = 123;
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (static_cast<object*> (db->load<base> (1)));
+ assert (p->bstr == "" && p->dstr == "" && p->num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<base> query;
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+ result r (db->query<base> (query::id == 1));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ object& o (static_cast<object&> (*i));
+ assert (o.bstr == "" && o.dstr == "" && o.num == 123);
+
+ try
+ {
+ db->query<base> (query::bstr == "ab"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () &&
+ i->bstr == "" && i->dstr == "" && i->num);
+
+ try
+ {
+ db->query<object> (query::dstr == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->bstr == "" && p->dstr == "" && p->num == 234);
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->bstr == "" && p->dstr == "" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<base> (2);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added section member in polymorphic classes.
+ //
+ {
+ using namespace test10;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o (1);
+ o.bstr = "ab";
+ o.dstr = "abc";
+ o.num = 123;
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p (db->load<base> (1));
+
+ try
+ {
+ db->load (*p, p->s); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<base> (2);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added members of a section in polymorphic classes.
+ //
+ {
+ using namespace test11;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o (1);
+ o.bstr = "ab";
+ o.dstr = "abc";
+ o.num = 123;
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p (db->load<base> (1));
+ db->load (*p, p->s);
+ object& o (static_cast<object&> (*p));
+ assert (o.bstr == "" && o.dstr == "" && o.num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<base> query;
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+ result r (db->query<base> (query::id == 1));
+ result::iterator i (r.begin ());
+ db->load (*i, i->s);
+ assert (i != r.end ());
+ object& o (static_cast<object&> (*i));
+ assert (o.bstr == "" && o.dstr == "" && o.num == 123);
+
+ try
+ {
+ db->query<base> (query::bstr == "ab"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ db->load (*i, i->s);
+ assert (i != r.end () &&
+ i->bstr == "" && i->dstr == "" && i->num);
+
+ try
+ {
+ db->query<object> (query::dstr == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "" && p->dstr == "" && p->num == 234);
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "" && p->dstr == "" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<base> (2);
+ t.commit ();
+ }
+
+ // Test empty statement detection in sections.
+ //
+ base b (3);
+ b.bstr = "bc";
+
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ unique_ptr<base> p (db->load<base> (3));
+ db->load (*p, p->s);
+ assert (p->bstr == "");
+ t.commit ();
+ }
+
+ b.bstr += 'd';
+ b.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (b);
+ unique_ptr<base> p (db->load<base> (3));
+ db->load (*p, p->s);
+ assert (p->bstr == "");
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (b);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added member and optimistic concurrency.
+ //
+ {
+ using namespace test12;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o (1);
+ o.str = "abc";
+ o.num = 123;
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "" && p->num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "" && i->num == 123);
+
+ try
+ {
+ db->query<object> (query::str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "" && p->num == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ unsigned long long v (o.v_);
+ db->update (o);
+ assert (o.v_ != v);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "" && p->num == 235 && p->v_ == o.v_);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added member in an object without id.
+ //
+ {
+ using namespace test13;
+
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o;
+ o.str = "abc";
+ o.num = 123;
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "" && i->num == 123);
+
+ try
+ {
+ db->query<object> (query::str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+
+ object o;
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+
+ result r (db->query<object> (query::num == 234));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "" && i->num == 234);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase_query<object> (query::num == 234);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added member in an object with auto id.
+ //
+ {
+ using namespace test14;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ unsigned long id;
+ {
+ object o;
+ o.str = "abc";
+ o.num = 123;
+
+ transaction t (db->begin ());
+ db->persist (o);
+ id = o.id;
+ t.commit ();
+ }
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (id));
+ assert (p->str == "" && p->num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "" && i->num == 123);
+
+ try
+ {
+ db->query<object> (query::str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+
+ t.commit ();
+ }
+
+ object o;
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "" && p->num == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (o.id);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added container member in a non-versioned object.
+ //
+ {
+ using namespace test21;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o (1);
+ o.num = 123;
+ o.vec.push_back (123);
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->num == 123 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->num == 123 && i->vec.empty ());
+ t.commit ();
+ }
+
+ object o (2);
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->num == 234 && p->vec.empty ());
+ t.commit ();
+ }
+
+ o.num++;
+ o.vec.modify (0)++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->num == 235 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added container member in a non-versioned section.
+ //
+ {
+ using namespace test22;
+
+ // None of the database operations should yet include the
+ // added members.
+ //
+ {
+ object o (1);
+ o.num = 123;
+ o.vec.push_back (123);
+
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->num == 123 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ assert (i->num == 123 && i->vec.empty ());
+ t.commit ();
+ }
+
+ object o (2);
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->num == 234 && p->vec.empty ());
+ t.commit ();
+ }
+
+ o.num++;
+ o.vec.modify (0)++; // No longer automatically marks as changed.
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->num == 234 && p->vec.empty ());
+ t.commit ();
+ }
+
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->num == 235 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ // Test basic soft-added member logic.
+ //
+ {
+ using namespace test2;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ p->str = "abc";
+ p->vec.push_back (123);
+ delete p->ptr;
+ p->ptr = new object1 (1);
+ db->update (*p);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "abc" && p->num == 123 &&
+ p->vec[0] == 123 && p->ptr->id_ == 1);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str.is_not_null () &&
+ query::ptr->id == 1));
+ result::iterator i (r.begin ());
+ assert (i != r.end () &&
+ i->str == "abc" && i->num == 123 &&
+ i->vec[0] == 123 && i->ptr->id_ == 1);
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+ o.ptr = new object1 (2);
+ o.ptr->ptrs.push_back (&o);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ db->persist (*o.ptr);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "bcd" && p->num == 234 &&
+ p->vec[0] == 234 && p->ptr->id_ == 2);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++;
+ delete o.ptr;
+ o.ptr = 0;
+
+ {
+ transaction t (db->begin ());
+ db->erase<object1> (2);
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "bcde" && p->num == 235 &&
+ p->vec[0] == 235 && p->ptr == 0);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test container with soft-added value member.
+ //
+ {
+ using namespace test5;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ p->vec.modify (0).str = "abc";
+ db->update (*p);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->vec[0].str == "abc" && p->vec[0].num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.vec.push_back (value ("bcd", 234));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->vec[0].str == "bcd" && p->vec[0].num == 234);
+ t.commit ();
+ }
+
+ o.vec.modify (0).str += 'e';
+ o.vec.modify (0).num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->vec[0].str == "bcde" && p->vec[0].num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test view with soft-added member.
+ //
+ {
+ using namespace test6;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ p->str = "abc";
+ db->update (*p);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<view> query;
+ typedef odb::result<view> result;
+
+ transaction t (db->begin ());
+ result r (db->query<view> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "abc" && i->num == 123);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added section member.
+ //
+ {
+ using namespace test7;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ p->str = "abc";
+ p->vec.push_back (123);
+ db->update (*p, p->s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->str == "abc" && p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ assert (i->str == "abc" && i->num == 123 && i->vec[0] == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "bcd" && p->num == 234 && p->vec[0] == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++; // Automatically marks section as updated.
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "bcde" && p->num == 235 && p->vec[0] == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added members of a section.
+ //
+ {
+ using namespace test8;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ p->str = "abc";
+ p->vec.push_back (123);
+ db->update (*p, p->s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->str == "abc" && p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ assert (i->str == "abc" && i->num == 123 && i->vec[0] == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "bcd" && p->num == 234 && p->vec[0] == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++; // Automatically marks section as updated.
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "bcde" && p->num == 235 && p->vec[0] == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test basic soft-added member logic in polymorphic classes.
+ //
+ {
+ using namespace test9;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ p->bstr = "ab";
+ p->dstr = "abc";
+ db->update (*p);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (static_cast<object*> (db->load<base> (1)));
+ assert (p->bstr == "ab" && p->dstr == "abc" && p->num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<base> query;
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+ result r (db->query<base> (query::bstr == "ab"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ object& o (static_cast<object&> (*i));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->bstr == "bc" && p->dstr == "bcd" && p->num == 234);
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->bstr == "bcd" && p->dstr == "bcde" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (static_cast<base&> (o));
+ t.commit ();
+ }
+ }
+
+ // Test soft-added section member in polymorphic classes.
+ //
+ {
+ using namespace test10;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ p->bstr = "ab";
+ p->dstr = "abc";
+ db->update (*p, p->s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p (db->load<base> (1));
+ db->load (*p, p->s);
+ object& o (static_cast<object&> (*p));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<base> query;
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+ result r (db->query<base> (query::bstr == "ab"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ object& o (static_cast<object&> (*i));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "bc" && p->dstr == "bcd" && p->num == 234);
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "bcd" && p->dstr == "bcde" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (static_cast<base&> (o));
+ t.commit ();
+ }
+ }
+
+ // Test soft-added members of a section in polymorphic classes.
+ //
+ {
+ using namespace test11;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ p->bstr = "ab";
+ p->dstr = "abc";
+ db->update (*p, p->s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p (db->load<base> (1));
+ db->load (*p, p->s);
+ object& o (static_cast<object&> (*p));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<base> query;
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+ result r (db->query<base> (query::bstr == "ab"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ object& o (static_cast<object&> (*i));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "bc" && p->dstr == "bcd" && p->num == 234);
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "bcd" && p->dstr == "bcde" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (static_cast<base&> (o));
+ t.commit ();
+ }
+ }
+
+ // Test soft-added member and optimistic concurrency.
+ //
+ {
+ using namespace test12;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ p->str = "abc";
+ db->update (*p);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "abc" && p->num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "abc" && i->num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "bcd" && p->num == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ unsigned long long v (o.v_);
+ db->update (o);
+ assert (o.v_ != v);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "bcde" && p->num == 235 && p->v_ == o.v_);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added member in an object without id.
+ //
+ {
+ using namespace test13;
+
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "abc" && i->num == 123);
+ t.commit ();
+ }
+
+ object o;
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+
+ result r (db->query<object> (query::str == "bcd"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "bcd" && i->num == 234);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase_query<object> (query::str == "bcd");
+ t.commit ();
+ }
+ }
+
+ // Test soft-added member in an object with auto id.
+ //
+ {
+ using namespace test14;
+
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ unsigned long id;
+ {
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ i->str = "abc";
+ db->update (*i);
+ id = i->id;
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (id));
+ assert (p->str == "abc" && p->num == 123);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "abc" && i->num == 123);
+ t.commit ();
+ }
+
+ object o;
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "bcd" && p->num == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "bcde" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added container member in a non-versioned object.
+ //
+ {
+ using namespace test21;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ p->vec.push_back (123);
+ db->update (*p);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->num == 123 && i->vec[0] == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->num == 234 && p->vec[0] == 234);
+ t.commit ();
+ }
+
+ o.num++;
+ o.vec.modify (0)++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->num == 235 && p->vec[0] == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added container member in a non-versioned section.
+ //
+ {
+ using namespace test22;
+
+ // All the database operations should now include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ p->vec.push_back (123);
+ db->update (*p, p->s);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ assert (i->num == 123 && i->vec[0] == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->num == 234 && p->vec[0] == 234);
+ t.commit ();
+ }
+
+ o.num++;
+ o.vec.modify (0)++; // Automatically marks section as changed.
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->num == 235 && p->vec[0] == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ // Test basic soft-added member logic.
+ //
+ {
+ using namespace test2;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "abc" && p->num == 123 &&
+ p->vec[0] == 123 && p->ptr->id_ == 1);
+ t.commit ();
+ }
+ }
+
+ // Test container with soft-added value member.
+ //
+ {
+ using namespace test5;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->vec[0].str == "abc" && p->vec[0].num == 123);
+ t.commit ();
+ }
+ }
+
+ // Test view with soft-added member.
+ //
+ {
+ using namespace test6;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ typedef odb::query<view> query;
+ typedef odb::result<view> result;
+
+ transaction t (db->begin ());
+ result r (db->query<view> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "abc" && i->num == 123);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added section member.
+ //
+ {
+ using namespace test7;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->str == "abc" && p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added members of a section.
+ //
+ {
+ using namespace test8;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->str == "abc" && p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+ }
+
+ // Test basic soft-added member logic in polymorphic classes.
+ //
+ {
+ using namespace test9;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (static_cast<object*> (db->load<base> (1)));
+ assert (p->bstr == "ab" && p->dstr == "abc" && p->num == 123);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added section member in polymorphic classes.
+ //
+ {
+ using namespace test10;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p (db->load<base> (1));
+ db->load (*p, p->s);
+ object& o (static_cast<object&> (*p));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added members of a section in polymorphic classes.
+ //
+ {
+ using namespace test11;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p (db->load<base> (1));
+ db->load (*p, p->s);
+ object& o (static_cast<object&> (*p));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added member and optimistic concurrency.
+ //
+ {
+ using namespace test12;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "abc" && p->num == 123);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added member in an object without id.
+ //
+ {
+ using namespace test13;
+
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "abc" && i->num == 123);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added member in an object with auto id.
+ //
+ {
+ using namespace test14;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "abc" && i->num == 123);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added container member in a non-versioned object.
+ //
+ {
+ using namespace test21;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+ }
+
+ // Test soft-added container member in a non-versioned section.
+ //
+ {
+ using namespace test22;
+
+ // All the database operations should still include the added
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+ }
+
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/soft-add/model.hxx b/odb-tests/evolution/soft-add/model.hxx
new file mode 100644
index 0000000..39d63c4
--- /dev/null
+++ b/odb-tests/evolution/soft-add/model.hxx
@@ -0,0 +1,504 @@
+// file : evolution/soft-add/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <string>
+
+#include <odb/core.hxx>
+#include <odb/vector.hxx>
+#include <odb/section.hxx>
+#include <odb/lazy-ptr.hxx>
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+ // The test numbers are made to correspond to the soft-delete ones.
+ //
+
+ // Test basic soft-added member logic.
+ //
+ #pragma db namespace table("t2_")
+ namespace test2
+ {
+ struct object;
+
+ #pragma db object
+ struct object1
+ {
+ object1 (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ odb::vector<odb::lazy_ptr<object> > ptrs;
+ };
+
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id), ptr (0) {}
+ ~object () {delete ptr;}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string str;
+ unsigned long num;
+ odb::vector<int> vec;
+
+ #pragma db inverse(ptrs)
+ object1* ptr;
+ };
+
+#if MODEL_VERSION == 3
+ // Make it a LOB for Oracle and long data for SQL Server.
+ //
+ #pragma db member(object::str) added(3) \
+ oracle:type("CLOB") \
+ mssql:type("VARCHAR(max)")
+ #pragma db member(object::vec) added(3)
+ #pragma db member(object::ptr) added(3)
+#else
+ #pragma db member(object::str) transient
+ #pragma db member(object::vec) transient
+ #pragma db member(object::ptr) transient
+#endif
+ }
+
+ // Test empty statement handling (INSERT, UPDATE).
+ //
+ #pragma db namespace table("t3_")
+ namespace test3
+ {
+ #pragma db object
+ struct object
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ std::string str;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) added(3)
+#else
+ #pragma db member(object::str) transient
+#endif
+ }
+
+ // Test empty statement handling (SELECT in polymorphic loader).
+ //
+ #pragma db namespace table("t4_")
+ namespace test4
+ {
+ #pragma db object polymorphic
+ struct base
+ {
+ virtual
+ ~base () {}
+ base (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+ };
+
+ #pragma db object
+ struct object: base
+ {
+ object (unsigned long id = 0): base (id) {}
+
+ std::string str;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) added(3)
+#else
+ #pragma db member(object::str) transient
+#endif
+ }
+
+ // Test container with soft-added value member.
+ //
+ #pragma db namespace table("t5_")
+ namespace test5
+ {
+ #pragma db value
+ struct value
+ {
+ value () {}
+ value (const std::string& s, unsigned long n): str (s), num (n) {}
+
+ std::string str;
+ unsigned long num;
+ };
+
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ odb::vector<value> vec;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(value::str) added(3)
+#else
+ #pragma db member(value::str) transient
+#endif
+ }
+
+ // Test view with soft-added member.
+ //
+ #pragma db namespace table("t6_")
+ namespace test6
+ {
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string str;
+ unsigned long num;
+ };
+
+ #pragma db view object(object)
+ struct view
+ {
+ std::string str;
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) added(3)
+ #pragma db member(view::str) added(3)
+#else
+ #pragma db member(object::str) transient
+ #pragma db member(view::str) transient
+#endif
+ }
+
+ // Test soft-added section member.
+ //
+ #pragma db namespace table("t7_")
+ namespace test7
+ {
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+#if MODEL_VERSION == 3
+ #pragma db load(lazy) update(change) added(3)
+ odb::section s;
+#endif
+
+ #pragma db section(s)
+ std::string str;
+
+ unsigned long num;
+
+ #pragma db section(s)
+ odb::vector<int> vec;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) section(s)
+ #pragma db member(object::vec) section(s)
+#else
+ #pragma db member(object::str) transient
+ #pragma db member(object::vec) transient
+#endif
+ }
+
+ // Test soft-added members of a section.
+ //
+ #pragma db namespace table("t8_")
+ namespace test8
+ {
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ std::string str;
+
+ #pragma db section(s)
+ unsigned long num;
+
+ odb::vector<int> vec;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) added(3) section(s)
+ #pragma db member(object::vec) added(3) section(s)
+#else
+ #pragma db member(object::str) transient
+ #pragma db member(object::vec) transient
+#endif
+ }
+
+ // Test basic soft-added member logic in polymorphic classes.
+ //
+ #pragma db namespace table("t9_")
+ namespace test9
+ {
+ #pragma db object polymorphic
+ struct base
+ {
+ virtual
+ ~base () {}
+ base (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string bstr;
+ };
+
+ #pragma db object
+ struct object: base
+ {
+ object (unsigned long id = 0): base (id) {}
+
+ std::string dstr;
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(base::bstr) added(3)
+ #pragma db member(object::dstr) added(3)
+#else
+ #pragma db member(base::bstr) transient
+ #pragma db member(object::dstr) transient
+#endif
+ }
+
+ // Test soft-added section member in polymorphic classes.
+ //
+ #pragma db namespace table("t10_")
+ namespace test10
+ {
+ #pragma db object polymorphic
+ struct base
+ {
+ virtual
+ ~base () {}
+ base (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+#if MODEL_VERSION == 3
+ #pragma db load(lazy) update(change) added(3)
+ odb::section s;
+#endif
+
+ std::string bstr;
+ };
+
+ #pragma db object
+ struct object: base
+ {
+ object (unsigned long id = 0): base (id) {}
+
+ std::string dstr;
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(base::bstr) section(s)
+ #pragma db member(object::dstr) section(s)
+#else
+ #pragma db member(base::bstr) transient
+ #pragma db member(object::dstr) transient
+#endif
+ }
+
+ // Test soft-added members of a section in polymorphic classes.
+ //
+ #pragma db namespace table("t11_")
+ namespace test11
+ {
+ #pragma db object polymorphic
+ struct base
+ {
+ virtual
+ ~base () {}
+ base (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ std::string bstr;
+ };
+
+ #pragma db object
+ struct object: base
+ {
+ object (unsigned long id = 0): base (id) {}
+
+ std::string dstr;
+
+ #pragma db section(s)
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(base::bstr) added(3) section(s)
+ #pragma db member(object::dstr) added(3) section(s)
+#else
+ #pragma db member(base::bstr) transient
+ #pragma db member(object::dstr) transient
+#endif
+ }
+
+ // Test soft-added member and optimistic concurrency.
+ //
+ #pragma db namespace table("t12_")
+ namespace test12
+ {
+ #pragma db object optimistic
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db version mssql:type("ROWVERSION")
+ unsigned long long v_;
+
+ std::string str;
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) added(3)
+#else
+ #pragma db member(object::str) transient
+#endif
+ }
+
+ // Test soft-added member in an object without id.
+ //
+ #pragma db namespace table("t13_")
+ namespace test13
+ {
+ #pragma db object no_id
+ struct object
+ {
+ std::string str;
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) added(3) default("abc") \
+ mysql:type("VARCHAR(255)")
+#else
+ #pragma db member(object::str) transient
+#endif
+ }
+
+ // Test soft-added member in an object with auto id.
+ //
+ #pragma db namespace table("t14_")
+ namespace test14
+ {
+ #pragma db object
+ struct object
+ {
+ std::string str;
+ unsigned long num;
+
+ #pragma db id auto
+ unsigned long id;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) added(3)
+#else
+ #pragma db member(object::str) transient
+#endif
+ }
+
+ // Test soft-added container member in a non-versioned object.
+ //
+ #pragma db namespace table("t21_")
+ namespace test21
+ {
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ unsigned long num;
+ odb::vector<int> vec;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::vec) added(3)
+#else
+ #pragma db member(object::vec) transient
+#endif
+ }
+
+ // Test soft-added container member in a non-versioned section.
+ //
+ #pragma db namespace table("t22_")
+ namespace test22
+ {
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ #pragma db section(s)
+ unsigned long num;
+
+ odb::vector<int> vec;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::vec) added(3) section(s)
+#else
+ #pragma db member(object::vec) transient
+#endif
+ }
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/soft-add/test1.hxx b/odb-tests/evolution/soft-add/test1.hxx
new file mode 100644
index 0000000..461d663
--- /dev/null
+++ b/odb-tests/evolution/soft-add/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/soft-add/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/soft-add/test2.hxx b/odb-tests/evolution/soft-add/test2.hxx
new file mode 100644
index 0000000..746da4b
--- /dev/null
+++ b/odb-tests/evolution/soft-add/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/soft-add/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/soft-add/test3.hxx b/odb-tests/evolution/soft-add/test3.hxx
new file mode 100644
index 0000000..f2990d0
--- /dev/null
+++ b/odb-tests/evolution/soft-add/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/soft-add/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/soft-add/testscript b/odb-tests/evolution/soft-add/testscript
new file mode 100644
index 0000000..5c26645
--- /dev/null
+++ b/odb-tests/evolution/soft-add/testscript
@@ -0,0 +1,58 @@
+# file : evolution/soft-add/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/soft-delete/buildfile b/odb-tests/evolution/soft-delete/buildfile
new file mode 100644
index 0000000..1d98505
--- /dev/null
+++ b/odb-tests/evolution/soft-delete/buildfile
@@ -0,0 +1,65 @@
+# file : evolution/soft-delete/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+
+import libs = libodb-$db%lib{odb-$db}
+import libs += lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the odb_compile ad hoc rule (see build/root.build for details).
+#
+libue{test-meta}: $libodb
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libs
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_soft_d_ \
+ --schema-version-table evo_soft_d_sv \
+ --generate-schema \
+ --generate-query \
+ --generate-prepared \
+ --at-once \
+ --changelog $out_base/model.xml \
+ --sqlite-override-null
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/soft-delete/driver.cxx b/odb-tests/evolution/soft-delete/driver.cxx
new file mode 100644
index 0000000..edc5db6
--- /dev/null
+++ b/odb-tests/evolution/soft-delete/driver.cxx
@@ -0,0 +1,2207 @@
+// file : evolution/soft-delete/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test soft-delete functionality.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_soft_d_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ // SQLite has broken foreign keys when it comes to DDL.
+ //
+#ifdef DATABASE_SQLITE
+ db->connection ()->execute ("PRAGMA foreign_keys=OFF");
+#endif
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+ schema_catalog::create_schema (*db, "", false);
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+
+#ifdef DATABASE_SQLITE
+ db->connection ()->execute ("PRAGMA foreign_keys=ON");
+#endif
+ }
+
+ // Test soft-deleted objects.
+ //
+ {
+ using namespace test1;
+
+ object o (1);
+ o.num = 123;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test basic soft-deleted member logic.
+ //
+ {
+ using namespace test2;
+
+ object o (1);
+ o.str = "abc";
+ o.num = 123;
+ o.vec.push_back (123);
+ o.ptr = new object1 (1);
+ o.ptr->ptrs.push_back (&o);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ db->persist (*o.ptr);
+ t.commit ();
+ }
+ }
+
+ // Test container with soft-deleted value member.
+ //
+ {
+ using namespace test5;
+
+ object o (1);
+ o.vec.push_back (value ("abc", 123));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test view with soft-deleted member.
+ //
+ {
+ using namespace test6;
+
+ object o (1);
+ o.str = "abc";
+ o.num = 123;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted section member.
+ //
+ {
+ using namespace test7;
+
+ object o (1);
+ o.str = "abc";
+ o.num = 123;
+ o.vec.push_back (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted members of a section.
+ //
+ {
+ using namespace test8;
+
+ object o (1);
+ o.str = "abc";
+ o.num = 123;
+ o.vec.push_back (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test basic soft-deleted member logic in polymorphic classes.
+ //
+ {
+ // We have to use v3 here because the discriminator includes
+ // the namespace.
+ //
+ using namespace v3::test9;
+
+ object o (1);
+ o.bstr = "ab";
+ o.dstr = "abc";
+ o.num = 123;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted section member in polymorphic classes.
+ //
+ {
+ // We have to use v3 here because the discriminator includes
+ // the namespace.
+ //
+ using namespace v3::test10;
+
+ object o (1);
+ o.bstr = "ab";
+ o.dstr = "abc";
+ o.num = 123;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted members of a section in polymorphic classes.
+ //
+ {
+ // We have to use v3 here because the discriminator includes
+ // the namespace.
+ //
+ using namespace v3::test11;
+
+ object o (1);
+ o.bstr = "ab";
+ o.dstr = "abc";
+ o.num = 123;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted member and optimistic concurrency.
+ //
+ {
+ using namespace test12;
+
+ object o (1);
+ o.str = "abc";
+ o.num = 123;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted member in an object without id.
+ //
+ {
+ using namespace test13;
+
+ object o;
+ o.str = "abc";
+ o.num = 123;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted member in an object with auto id.
+ //
+ {
+ using namespace test14;
+
+ object o;
+ o.str = "abc";
+ o.num = 123;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test summarily deleted composite values.
+ //
+ {
+ using namespace test15;
+
+ object o (1);
+ o.v.reset (new value);
+ o.v->str = "abc";
+ o.v->vec.push_back (123);
+ o.num = 123;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted container member in a non-versioned object.
+ //
+ {
+ using namespace test21;
+
+ object o (1);
+ o.num = 123;
+ o.vec.push_back (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted container member in a non-versioned section.
+ //
+ {
+ using namespace test22;
+
+ object o (1);
+ o.num = 123;
+ o.vec.push_back (123);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+ }
+
+ break;
+ }
+ case 2:
+ {
+ using namespace v3;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ // Test soft-deleted objects.
+ //
+ {
+ using namespace test1;
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->num == 123);
+ t.commit ();
+ }
+ }
+
+ // Test basic soft-deleted member logic.
+ //
+ {
+ using namespace test2;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "abc" && p->num == 123 &&
+ p->vec[0] == 123 && p->ptr->id_ == 1);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc" &&
+ query::ptr->id == 1));
+ result::iterator i (r.begin ());
+ assert (i != r.end () &&
+ i->str == "abc" && i->num == 123 &&
+ i->vec[0] == 123 && i->ptr->id_ == 1);
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+ o.ptr = new object1 (2);
+ o.ptr->ptrs.push_back (&o);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ db->persist (*o.ptr);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "bcd" && p->num == 234 &&
+ p->vec[0] == 234 && p->ptr->id_ == 2);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++;
+ delete o.ptr;
+ o.ptr = 0;
+
+ {
+ transaction t (db->begin ());
+ db->erase<object1> (2);
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "bcde" && p->num == 235 &&
+ p->vec[0] == 235 && p->ptr == 0);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test container with soft-deleted value member.
+ //
+ {
+ using namespace test5;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->vec[0].str == "abc" && p->vec[0].num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.vec.push_back (value ("bcd", 234));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->vec[0].str == "bcd" && p->vec[0].num == 234);
+ t.commit ();
+ }
+
+ o.vec.modify (0).str += 'e';
+ o.vec.modify (0).num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->vec[0].str == "bcde" && p->vec[0].num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test view with soft-deleted member.
+ //
+ {
+ using namespace test6;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ typedef odb::query<view> query;
+ typedef odb::result<view> result;
+
+ transaction t (db->begin ());
+ result r (db->query<view> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "abc" && i->num == 123);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted section member.
+ //
+ {
+ using namespace test7;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->str == "abc" && p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ assert (i->str == "abc" && i->num == 123 && i->vec[0] == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "bcd" && p->num == 234 && p->vec[0] == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++; // Automatically marks section as updated.
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "bcde" && p->num == 235 && p->vec[0] == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted members of a section.
+ //
+ {
+ using namespace test8;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->str == "abc" && p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ assert (i->str == "abc" && i->num == 123 && i->vec[0] == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "bcd" && p->num == 234 && p->vec[0] == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++; // Automatically marks section as updated.
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "bcde" && p->num == 235 && p->vec[0] == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test basic soft-deleted member logic in polymorphic classes.
+ //
+ {
+ using namespace test9;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (static_cast<object*> (db->load<base> (1)));
+ assert (p->bstr == "ab" && p->dstr == "abc" && p->num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<base> query;
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+ result r (db->query<base> (query::bstr == "ab"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ object& o (static_cast<object&> (*i));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->bstr == "bc" && p->dstr == "bcd" && p->num == 234);
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->bstr == "bcd" && p->dstr == "bcde" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (static_cast<base&> (o));
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted section member in polymorphic classes.
+ //
+ {
+ using namespace test10;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p (db->load<base> (1));
+ db->load (*p, p->s);
+ object& o (static_cast<object&> (*p));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<base> query;
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+ result r (db->query<base> (query::bstr == "ab"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ object& o (static_cast<object&> (*i));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "bc" && p->dstr == "bcd" && p->num == 234);
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "bcd" && p->dstr == "bcde" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (static_cast<base&> (o));
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted members of a section in polymorphic classes.
+ //
+ {
+ using namespace test11;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p (db->load<base> (1));
+ db->load (*p, p->s);
+ object& o (static_cast<object&> (*p));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<base> query;
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+ result r (db->query<base> (query::bstr == "ab"));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ object& o (static_cast<object&> (*i));
+ assert (o.bstr == "ab" && o.dstr == "abc" && o.num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "bc" && p->dstr == "bcd" && p->num == 234);
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "bcd" && p->dstr == "bcde" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (static_cast<base&> (o));
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted member and optimistic concurrency.
+ //
+ {
+ using namespace test12;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "abc" && p->num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "abc" && i->num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "bcd" && p->num == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ unsigned long long v (o.v_);
+ db->update (o);
+ assert (o.v_ != v);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "bcde" && p->num == 235 && p->v_ == o.v_);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted member in an object without id.
+ //
+ {
+ using namespace test13;
+
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "abc" && i->num == 123);
+ t.commit ();
+ }
+
+ object o;
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+
+ result r (db->query<object> (query::str == "bcd"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "bcd" && i->num == 234);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase_query<object> (query::str == "bcd");
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted member in an object with auto id.
+ //
+ {
+ using namespace test14;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ unsigned long id;
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "abc" && i->num == 123);
+ id = i->id;
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (id));
+ assert (p->str == "abc" && p->num == 123);
+ t.commit ();
+ }
+
+ object o;
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "bcd" && p->num == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "bcde" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test summarily deleted composite values.
+ //
+ {
+ using namespace test15;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->v->str == "abc" && p->num == 123 &&
+ p->v->vec[0] == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::v.str == "abc"));
+ result::iterator i (r.begin ());
+ assert (i != r.end () &&
+ i->v->str == "abc" && i->num == 123 &&
+ i->v->vec[0] == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.v.reset (new value);
+ o.v->str = "bcd";
+ o.num = 234;
+ o.v->vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->v->str == "bcd" && p->num == 234 &&
+ p->v->vec[0] == 234);
+ t.commit ();
+ }
+
+ o.v->str += 'e';
+ o.num++;
+ o.v->vec.modify (0)++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->v->str == "bcde" && p->num == 235 &&
+ p->v->vec[0] == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted container member in a non-versioned object.
+ //
+ {
+ using namespace test21;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->num == 123 && i->vec[0] == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->num == 234 && p->vec[0] == 234);
+ t.commit ();
+ }
+
+ o.num++;
+ o.vec.modify (0)++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->num == 235 && p->vec[0] == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted container member in a non-versioned section.
+ //
+ {
+ using namespace test22;
+
+ // All the database operations should still include the deleted
+ // members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->num == 123 && p->vec[0] == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ assert (i->num == 123 && i->vec[0] == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->num == 234 && p->vec[0] == 234);
+ t.commit ();
+ }
+
+ o.num++;
+ o.vec.modify (0)++; // Automatically marks section as changed.
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->num == 235 && p->vec[0] == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase (o);
+ t.commit ();
+ }
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ // Test soft-deleted objects.
+ //
+ {
+ using namespace test1;
+
+ try
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1)); // No such table.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+ }
+
+ // Test basic soft-deleted member logic.
+ //
+ {
+ using namespace test2;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "" && p->num == 123 &&
+ p->vec.empty () && p->ptr == 0);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () &&
+ i->str == "" && i->num == 123 &&
+ i->vec.empty () && i->ptr == 0);
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->query<object> (query::str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#else
+ assert (size (db->query<object> (query::str.is_null ())) == 1);
+#endif
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+ o.ptr = new object1 (2);
+ o.ptr->ptrs.push_back (&o);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ db->persist (*o.ptr);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "" && p->num == 234 &&
+ p->vec.empty () && p->ptr == 0);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++;
+ delete o.ptr;
+ o.ptr = 0;
+
+ {
+ transaction t (db->begin ());
+ db->erase<object1> (2);
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "" && p->num == 235 &&
+ p->vec.empty () && p->ptr == 0);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test empty statement handling (INSERT, UPDATE).
+ //
+ {
+ using namespace test3;
+
+ // Now none of the database operations should include the
+ // deleted member.
+ //
+ object o;
+ o.str = "bcd";
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "");
+ t.commit ();
+ }
+
+ o.str += 'e';
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "");
+ t.commit ();
+ }
+ }
+
+ // Test empty statement handling (SELECT in polymorphic loader).
+ //
+ {
+ using namespace test4;
+
+ // Now none of the database operations should include the
+ // deleted member.
+ //
+ object o (1);
+ o.str = "abc";
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<base> p (db->load<base> (1));
+ assert (static_cast<object&> (*p).str == "");
+ t.commit ();
+ }
+ }
+
+ // Test container with soft-deleted value member.
+ //
+ {
+ using namespace test5;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->vec[0].str == "" && p->vec[0].num == 123);
+ t.commit ();
+ }
+
+ object o (2);
+ o.vec.push_back (value ("bcd", 234));
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->vec[0].str == "" && p->vec[0].num == 234);
+ t.commit ();
+ }
+
+ o.vec.modify (0).str += 'e';
+ o.vec.modify (0).num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->vec[0].str == "" && p->vec[0].num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test view with soft-deleted member.
+ //
+ {
+ using namespace test6;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ typedef odb::query<view> query;
+ typedef odb::result<view> result;
+
+ transaction t (db->begin ());
+ result r (db->query<view> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "" && i->num == 123);
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->query<object> (query::str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#else
+ assert (size (db->query<object> (query::str.is_null ())) == 1);
+#endif
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted section member.
+ //
+ {
+ using namespace test7;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->load (*p, p->s); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#endif
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++;
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted members of a section.
+ //
+ {
+ using namespace test8;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->str == "" && p->num == 123 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ assert (i->str == "" && i->num == 123 && i->vec.empty ());
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->query<object> (query::str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#else
+ assert (size (db->query<object> (query::str.is_null ())) == 1);
+#endif
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "" && p->num == 234 && p->vec.empty ());
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+ o.vec.modify (0)++; // No longer automatically marked as changed.
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "" && p->num == 234 && p->vec.empty ());
+ t.commit ();
+ }
+
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->str == "" && p->num == 235 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test basic soft-deleted member logic in polymorphic classes.
+ //
+ {
+ using namespace test9;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (static_cast<object*> (db->load<base> (1)));
+ assert (p->bstr == "" && p->dstr == "" && p->num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<base> query;
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+ result r (db->query<base> (query::id == 1));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ object& o (static_cast<object&> (*i));
+ assert (o.bstr == "" && o.dstr == "" && o.num == 123);
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->query<base> (query::bstr == "ab"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#else
+ assert (size (db->query<base> (query::bstr.is_null ())) == 1);
+#endif
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () &&
+ i->bstr == "" && i->dstr == "" && i->num);
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->query<object> (query::dstr == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#else
+ assert (size (db->query<object> (query::dstr.is_null ())) == 1);
+#endif
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->bstr == "" && p->dstr == "" && p->num == 234);
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->bstr == "" && p->dstr == "" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<base> (2);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted section member in polymorphic classes.
+ //
+ {
+ using namespace test10;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p (db->load<base> (1));
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->load (*p, p->s); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#endif
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<base> (2);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted members of a section in polymorphic classes.
+ //
+ {
+ using namespace test11;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<base> p (db->load<base> (1));
+ db->load (*p, p->s);
+ object& o (static_cast<object&> (*p));
+ assert (o.bstr == "" && o.dstr == "" && o.num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<base> query;
+ typedef odb::result<base> result;
+
+ transaction t (db->begin ());
+ result r (db->query<base> (query::id == 1));
+ result::iterator i (r.begin ());
+ db->load (*i, i->s);
+ assert (i != r.end ());
+ object& o (static_cast<object&> (*i));
+ assert (o.bstr == "" && o.dstr == "" && o.num == 123);
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->query<base> (query::bstr == "ab"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#else
+ assert (size (db->query<base> (query::bstr.is_null ())) == 1);
+#endif
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ db->load (*i, i->s);
+ assert (i != r.end () &&
+ i->bstr == "" && i->dstr == "" && i->num);
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->query<object> (query::dstr == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#else
+ assert (size (db->query<object> (query::dstr.is_null ())) == 1);
+#endif
+ t.commit ();
+ }
+
+ object o (2);
+ o.bstr = "bc";
+ o.dstr = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "" && p->dstr == "" && p->num == 234);
+ t.commit ();
+ }
+
+ o.bstr += 'd';
+ o.dstr += 'e';
+ o.num++;
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (static_cast<base&> (o));
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->bstr == "" && p->dstr == "" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<base> (2);
+ t.commit ();
+ }
+
+ // Test empty statement detection in sections.
+ //
+ base b (3);
+ b.bstr = "bc";
+
+ {
+ transaction t (db->begin ());
+ db->persist (b);
+ unique_ptr<base> p (db->load<base> (3));
+ db->load (*p, p->s);
+ assert (p->bstr == "");
+ t.commit ();
+ }
+
+ b.bstr += 'd';
+ b.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (b);
+ unique_ptr<base> p (db->load<base> (3));
+ db->load (*p, p->s);
+ assert (p->bstr == "");
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted member and optimistic concurrency.
+ //
+ {
+ using namespace test12;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->str == "" && p->num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "" && i->num == 123);
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->query<object> (query::str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#else
+ assert (size (db->query<object> (query::str.is_null ())) == 1);
+#endif
+ t.commit ();
+ }
+
+ object o (2);
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "" && p->num == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ unsigned long long v (o.v_);
+ db->update (o);
+ assert (o.v_ != v);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->str == "" && p->num == 235 && p->v_ == o.v_);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted member in an object without id.
+ //
+ {
+ using namespace test13;
+
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "" && i->num == 123);
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->query<object> (query::str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#else
+ assert (size (db->query<object> (query::str.is_null ())) == 1);
+#endif
+ t.commit ();
+ }
+
+ object o;
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+
+ result r (db->query<object> (query::num == 234));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "" && i->num == 234);
+
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase_query<object> (query::num == 234);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted member in an object with auto id.
+ //
+ {
+ using namespace test14;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ unsigned long id;
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->str == "" && i->num == 123);
+ id = i->id;
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->query<object> (query::str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#else
+ assert (size (db->query<object> (query::str.is_null ())) == 1);
+#endif
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (id));
+ assert (p->str == "" && p->num == 123);
+ t.commit ();
+ }
+
+ object o;
+ o.str = "bcd";
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "" && p->num == 234);
+ t.commit ();
+ }
+
+ o.str += 'e';
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (o.id));
+ assert (p->str == "" && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (o.id);
+ t.commit ();
+ }
+ }
+
+ // Test summarily deleted composite values.
+ //
+ {
+ using namespace test15;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->v.get () == 0 && p->num == 123);
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->v.get () == 0 && i->num == 123);
+
+ // Logical delete in SQLite.
+ //
+#ifndef DATABASE_SQLITE
+ try
+ {
+ db->query<object> (query::v.str == "abc"); // No such column.
+ assert (false);
+ }
+ catch (const odb::exception&) {}
+#else
+ assert (size (db->query<object> (query::v.str.is_null ())) == 1);
+#endif
+ t.commit ();
+ }
+
+ object o (2);
+ o.num = 234;
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->v.get () == 0 && p->num == 234);
+ t.commit ();
+ }
+
+ o.num++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->v.get () == 0 && p->num == 235);
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted container member in a non-versioned object.
+ //
+ {
+ using namespace test21;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ assert (p->num == 123 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end () && i->num == 123 && i->vec.empty ());
+ t.commit ();
+ }
+
+ object o (2);
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->num == 234 && p->vec.empty ());
+ t.commit ();
+ }
+
+ o.num++;
+ o.vec.modify (0)++;
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ assert (p->num == 235 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ // Test soft-deleted container member in a non-versioned section.
+ //
+ {
+ using namespace test22;
+
+ // Now none of the database operations should include the
+ // deleted members.
+ //
+ {
+ transaction t (db->begin ());
+ unique_ptr<object> p (db->load<object> (1));
+ db->load (*p, p->s);
+ assert (p->num == 123 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ typedef odb::query<object> query;
+ typedef odb::result<object> result;
+
+ transaction t (db->begin ());
+ result r (db->query<object> (query::num == 123));
+ result::iterator i (r.begin ());
+ assert (i != r.end ());
+ db->load (*i, i->s);
+ assert (i->num == 123 && i->vec.empty ());
+ t.commit ();
+ }
+
+ object o (2);
+ o.num = 234;
+ o.vec.push_back (234);
+
+ {
+ transaction t (db->begin ());
+ db->persist (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->num == 234 && p->vec.empty ());
+ t.commit ();
+ }
+
+ o.num++;
+ o.vec.modify (0)++; // No longer automatically marks as changed.
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->num == 234 && p->vec.empty ());
+ t.commit ();
+ }
+
+ o.s.change ();
+
+ {
+ transaction t (db->begin ());
+ db->update (o);
+ unique_ptr<object> p (db->load<object> (2));
+ db->load (*p, p->s);
+ assert (p->num == 235 && p->vec.empty ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ db->erase<object> (2);
+ t.commit ();
+ }
+ }
+
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/soft-delete/model.hxx b/odb-tests/evolution/soft-delete/model.hxx
new file mode 100644
index 0000000..093aadf
--- /dev/null
+++ b/odb-tests/evolution/soft-delete/model.hxx
@@ -0,0 +1,512 @@
+// file : evolution/soft-delete/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <string>
+#include <memory> // unique_ptr
+
+#include <odb/core.hxx>
+#include <odb/vector.hxx>
+#include <odb/section.hxx>
+#include <odb/lazy-ptr.hxx>
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+ // Test soft-deleted objects.
+ //
+ #pragma db namespace table("t1_")
+ namespace test1
+ {
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db object(object) deleted(3)
+#endif
+ }
+
+ // Test basic soft-deleted member logic.
+ //
+ #pragma db namespace table("t2_")
+ namespace test2
+ {
+ struct object;
+
+ #pragma db object
+ struct object1
+ {
+ object1 (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ odb::vector<odb::lazy_ptr<object> > ptrs;
+ };
+
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id), ptr (0) {}
+ ~object () {delete ptr;}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string str;
+ unsigned long num;
+ odb::vector<int> vec;
+
+ #pragma db inverse(ptrs)
+ object1* ptr;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) deleted(3)
+ #pragma db member(object::vec) deleted(3)
+ #pragma db member(object::ptr) deleted(3)
+#endif
+ }
+
+ // Test empty statement handling (INSERT, UPDATE).
+ //
+ #pragma db namespace table("t3_")
+ namespace test3
+ {
+ #pragma db object
+ struct object
+ {
+ #pragma db id auto
+ unsigned long id;
+
+ std::string str;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) deleted(3)
+#endif
+ }
+
+ // Test empty statement handling (SELECT in polymorphic loader).
+ //
+ #pragma db namespace table("t4_")
+ namespace test4
+ {
+ #pragma db object polymorphic
+ struct base
+ {
+ virtual
+ ~base () {}
+ base (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+ };
+
+ #pragma db object
+ struct object: base
+ {
+ object (unsigned long id = 0): base (id) {}
+
+ std::string str;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) deleted(3)
+#endif
+ }
+
+ // Test container with soft-deleted value member.
+ //
+ #pragma db namespace table("t5_")
+ namespace test5
+ {
+ #pragma db value
+ struct value
+ {
+ value () {}
+ value (const std::string& s, unsigned long n): str (s), num (n) {}
+
+ std::string str;
+ unsigned long num;
+ };
+
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ odb::vector<value> vec;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(value::str) deleted(3)
+#endif
+ }
+
+ // Test view with soft-deleted member.
+ //
+ #pragma db namespace table("t6_")
+ namespace test6
+ {
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string str;
+ unsigned long num;
+ };
+
+ #pragma db view object(object)
+ struct view
+ {
+ std::string str;
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) deleted(3)
+ #pragma db member(view::str) deleted(3)
+#endif
+ }
+
+ // Test soft-deleted section member.
+ //
+ #pragma db namespace table("t7_")
+ namespace test7
+ {
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ #pragma db section(s)
+ std::string str;
+
+ unsigned long num;
+
+ #pragma db section(s)
+ odb::vector<int> vec;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::s) deleted(3)
+#endif
+ }
+
+ // Test soft-deleted members of a section.
+ //
+ #pragma db namespace table("t8_")
+ namespace test8
+ {
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ #pragma db section(s)
+ std::string str;
+
+ #pragma db section(s)
+ unsigned long num;
+
+ #pragma db section(s)
+ odb::vector<int> vec;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) deleted(3)
+ #pragma db member(object::vec) deleted(3)
+#endif
+ }
+
+ // Test basic soft-deleted member logic in polymorphic classes.
+ //
+ #pragma db namespace table("t9_")
+ namespace test9
+ {
+ #pragma db object polymorphic
+ struct base
+ {
+ virtual
+ ~base () {}
+ base (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::string bstr;
+ };
+
+ #pragma db object
+ struct object: base
+ {
+ object (unsigned long id = 0): base (id) {}
+
+ std::string dstr;
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(base::bstr) deleted(3)
+ #pragma db member(object::dstr) deleted(3)
+#endif
+ }
+
+ // Test soft-deleted section member in polymorphic classes.
+ //
+ #pragma db namespace table("t10_")
+ namespace test10
+ {
+ #pragma db object polymorphic
+ struct base
+ {
+ virtual
+ ~base () {}
+ base (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ #pragma db section(s)
+ std::string bstr;
+ };
+
+ #pragma db object
+ struct object: base
+ {
+ object (unsigned long id = 0): base (id) {}
+
+ #pragma db section(s)
+ std::string dstr;
+
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(base::s) deleted(3)
+#endif
+ }
+
+ // Test soft-deleted members of a section in polymorphic classes.
+ //
+ #pragma db namespace table("t11_")
+ namespace test11
+ {
+ #pragma db object polymorphic
+ struct base
+ {
+ virtual
+ ~base () {}
+ base (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ #pragma db section(s)
+ std::string bstr;
+ };
+
+ #pragma db object
+ struct object: base
+ {
+ object (unsigned long id = 0): base (id) {}
+
+ #pragma db section(s)
+ std::string dstr;
+
+ #pragma db section(s)
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(base::bstr) deleted(3)
+ #pragma db member(object::dstr) deleted(3)
+#endif
+ }
+
+ // Test soft-deleted member and optimistic concurrency.
+ //
+ #pragma db namespace table("t12_")
+ namespace test12
+ {
+ #pragma db object optimistic
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db version mssql:type("ROWVERSION")
+ unsigned long long v_;
+
+ std::string str;
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) deleted(3)
+#endif
+ }
+
+ // Test soft-deleted member in an object without id.
+ //
+ #pragma db namespace table("t13_")
+ namespace test13
+ {
+ #pragma db object no_id
+ struct object
+ {
+ std::string str;
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) deleted(3)
+#endif
+ }
+
+ // Test soft-deleted member in an object with auto id.
+ //
+ #pragma db namespace table("t14_")
+ namespace test14
+ {
+ #pragma db object
+ struct object
+ {
+ std::string str;
+ unsigned long num;
+
+ #pragma db id auto
+ unsigned long id;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::str) deleted(3)
+#endif
+ }
+
+ // Test summarily deleted composite values.
+ //
+ #pragma db namespace table("t15_")
+ namespace test15
+ {
+ #pragma db value
+ struct value
+ {
+ std::string str;
+ odb::vector<int> vec;
+ };
+
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ std::unique_ptr<value> v;
+ unsigned long num;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(value::str) deleted(3)
+ #pragma db member(value::vec) deleted(3)
+#endif
+ }
+
+ // Test soft-deleted container member in a non-versioned object.
+ //
+ #pragma db namespace table("t21_")
+ namespace test21
+ {
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ unsigned long num;
+ odb::vector<int> vec;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::vec) deleted(3)
+#endif
+ }
+
+ // Test soft-deleted container member in a non-versioned section.
+ //
+ #pragma db namespace table("t22_")
+ namespace test22
+ {
+ #pragma db object
+ struct object
+ {
+ object (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ #pragma db load(lazy) update(change)
+ odb::section s;
+
+ #pragma db section(s)
+ unsigned long num;
+
+ #pragma db section(s)
+ odb::vector<int> vec;
+ };
+
+#if MODEL_VERSION == 3
+ #pragma db member(object::vec) deleted(3)
+#endif
+ }
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/soft-delete/test1.hxx b/odb-tests/evolution/soft-delete/test1.hxx
new file mode 100644
index 0000000..d4df90f
--- /dev/null
+++ b/odb-tests/evolution/soft-delete/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/soft-delete/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/soft-delete/test2.hxx b/odb-tests/evolution/soft-delete/test2.hxx
new file mode 100644
index 0000000..3b2b5b4
--- /dev/null
+++ b/odb-tests/evolution/soft-delete/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/soft-delete/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/soft-delete/test3.hxx b/odb-tests/evolution/soft-delete/test3.hxx
new file mode 100644
index 0000000..5a90ab2
--- /dev/null
+++ b/odb-tests/evolution/soft-delete/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/soft-delete/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/soft-delete/testscript b/odb-tests/evolution/soft-delete/testscript
new file mode 100644
index 0000000..63e81e6
--- /dev/null
+++ b/odb-tests/evolution/soft-delete/testscript
@@ -0,0 +1,58 @@
+# file : evolution/soft-delete/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}
diff --git a/odb-tests/evolution/version/buildfile b/odb-tests/evolution/version/buildfile
new file mode 100644
index 0000000..f11559f
--- /dev/null
+++ b/odb-tests/evolution/version/buildfile
@@ -0,0 +1,63 @@
+# file : evolution/version/buildfile
+# license : GNU GPL v2; see accompanying LICENSE file
+
+if ($build.meta_operation != 'dist')
+ assert (!$multi) "multi-database mode is not supported by this test"
+
+db = ($databases[0])
+
+import libodb = libodb%lib{odb}
+
+import libs = libodb-$db%lib{odb-$db}
+import libs += lib{common}
+
+hdrs = test1 test2 test3
+
+exe{driver}: {hxx cxx}{* -*-odb} testscript
+
+# Introduce the metadata library target to make sure the libodb library is
+# resolved for the odb_compile ad hoc rule (see build/root.build for details).
+#
+libue{test-meta}: $libodb
+
+for h: $hdrs
+ exe{driver}: {hxx ixx cxx}{$h-odb}: hxx{$h} libue{test-meta} hxx{model}
+
+# Make sure testN.hxx are compiled serially since they share the changelog.
+#
+# @@ TODO: make order-only when supported by build2.
+#
+{hxx ixx cxx}{test3-odb}: {hxx ixx cxx}{test2-odb}: {hxx ixx cxx}{test1-odb}
+
+exe{driver}: libue{test-meta} $libs
+
+# Specify the ODB custom options to be used by the odb_compile ad hoc rule
+# (see build/root.build for details).
+#
+odb_options = --table-prefix evo_version_ \
+ --schema-version-table evo_version_sv \
+ --generate-schema \
+ --generate-query \
+ --at-once \
+ --changelog $out_base/model.xml
+
+<{hxx ixx cxx}{test1-odb}>: odb_options += --init-changelog
+<{hxx ixx cxx}{test2-odb}>: odb_options += --omit-create --suppress-migration
+
+<{hxx ixx cxx}{test3-odb}>:
+{
+ odb_options += --omit-create
+ schema_versions = 002 003
+}
+
+cxx.poptions =+ "-I$out_base" "-I$src_base"
+
+# Testscript's run-time prerequisites.
+#
+exe{driver}: ../../alias{database-client}: include = adhoc
+
+testscript@./:
+{
+ db = $db
+ schemas = $hdrs
+}
diff --git a/odb-tests/evolution/version/driver.cxx b/odb-tests/evolution/version/driver.cxx
new file mode 100644
index 0000000..91aaeaa
--- /dev/null
+++ b/odb-tests/evolution/version/driver.cxx
@@ -0,0 +1,161 @@
+// file : evolution/version/driver.cxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+// Test schema version access via the database instance.
+//
+
+#include <memory> // std::unique_ptr
+#include <iostream>
+
+#include <odb/database.hxx>
+#include <odb/transaction.hxx>
+#include <odb/schema-catalog.hxx>
+
+#include <libcommon/config.hxx> // DATABASE_XXX
+#include <libcommon/common.hxx>
+
+#include "test2.hxx"
+#include "test3.hxx"
+#include "test2-odb.hxx"
+#include "test3-odb.hxx"
+
+#undef NDEBUG
+#include <cassert>
+
+using namespace std;
+using namespace odb::core;
+
+int
+main (int argc, char* argv[])
+{
+ try
+ {
+ unique_ptr<database> db (create_database (argc, argv, false));
+
+ db->schema_version_table (quote_name ("evo_version_sv"));
+
+ bool embedded (schema_catalog::exists (*db));
+
+ // 1 - base version
+ // 2 - migration
+ // 3 - current version
+ //
+ unsigned short pass (*argv[argc - 1] - '0');
+
+ switch (pass)
+ {
+ case 1:
+ {
+ using namespace v2;
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::drop_schema (*db);
+
+ assert (db->schema_version () == 0);
+
+ schema_catalog::create_schema (*db, "", false);
+
+ assert (db->schema_version () == 1 && !db->schema_migration ());
+
+ schema_catalog::migrate_schema (*db, 2);
+ t.commit ();
+ }
+
+ assert (db->schema_version () == 2 && !db->schema_migration ());
+
+ {
+ transaction t (db->begin ());
+ object1 o1 (1);
+ o1.num = 123;
+ db->persist (o1);
+ t.commit ();
+ }
+ break;
+ }
+ case 2:
+ {
+ using namespace v2;
+ using namespace v3;
+
+ if (embedded)
+ {
+ assert (db->schema_version () == 2 && !db->schema_migration ());
+
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_pre (*db, 3);
+ t.commit ();
+ }
+
+ assert (db->schema_version () == 3 && db->schema_migration ());
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object1> o1 (db->load<object1> (1));
+ object2 o2 (1);
+ o2.num = o1->num;
+ db->persist (o2);
+ t.commit ();
+ }
+
+ if (embedded)
+ {
+ transaction t (db->begin ());
+ schema_catalog::migrate_schema_post (*db, 3);
+ t.commit ();
+
+ assert (db->schema_version () == 3 && !db->schema_migration ());
+ }
+ break;
+ }
+ case 3:
+ {
+ using namespace v3;
+
+ // In transaction.
+ //
+ {
+ transaction t (db->begin ());
+ assert (db->schema_version () == 3 && !db->schema_migration ());
+ t.commit ();
+ }
+
+ {
+ transaction t (db->begin ());
+ unique_ptr<object2> o2 (db->load<object2> (1));
+ assert (o2->num == 123);
+ t.commit ();
+ }
+
+ // Test the case where there is still no version table.
+ //
+ db->schema_version_migration (0, false);
+
+ {
+ transaction t (db->begin ());
+
+#ifdef DATABASE_ORACLE
+ db->execute ("DROP TABLE \"evo_version_sv\"");
+#else
+ db->execute ("DROP TABLE evo_version_sv");
+#endif
+ t.commit ();
+ }
+
+ assert (db->schema_version () == 0);
+ break;
+ }
+ default:
+ {
+ cerr << "unknown pass number '" << argv[argc - 1] << "'" << endl;
+ return 1;
+ }
+ }
+ }
+ catch (const odb::exception& e)
+ {
+ cerr << e.what () << endl;
+ return 1;
+ }
+}
diff --git a/odb-tests/evolution/version/model.hxx b/odb-tests/evolution/version/model.hxx
new file mode 100644
index 0000000..cdda00e
--- /dev/null
+++ b/odb-tests/evolution/version/model.hxx
@@ -0,0 +1,45 @@
+// file : evolution/version/model.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef MODEL_VERSION
+# error model.hxx included directly
+#endif
+
+#include <odb/core.hxx>
+
+#pragma db model version(1, MODEL_VERSION)
+
+#define MODEL_NAMESPACE_IMPL(V) v##V
+#define MODEL_NAMESPACE(V) MODEL_NAMESPACE_IMPL(V)
+
+namespace MODEL_NAMESPACE(MODEL_VERSION)
+{
+#if MODEL_VERSION == 2
+ #pragma db object
+ struct object1
+ {
+ object1 (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ int num;
+ };
+#endif
+
+#if MODEL_VERSION == 3
+ #pragma db object
+ struct object2
+ {
+ object2 (unsigned long id = 0): id_ (id) {}
+
+ #pragma db id
+ unsigned long id_;
+
+ int num;
+ };
+#endif
+}
+
+#undef MODEL_NAMESPACE
+#undef MODEL_NAMESPACE_IMPL
diff --git a/odb-tests/evolution/version/test1.hxx b/odb-tests/evolution/version/test1.hxx
new file mode 100644
index 0000000..a50e54c
--- /dev/null
+++ b/odb-tests/evolution/version/test1.hxx
@@ -0,0 +1,9 @@
+// file : evolution/version/test1.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST1_HXX
+#define TEST1_HXX
+
+#pragma db model version(1, 1)
+
+#endif // TEST1_HXX
diff --git a/odb-tests/evolution/version/test2.hxx b/odb-tests/evolution/version/test2.hxx
new file mode 100644
index 0000000..f7fc1b7
--- /dev/null
+++ b/odb-tests/evolution/version/test2.hxx
@@ -0,0 +1,11 @@
+// file : evolution/version/test2.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST2_HXX
+#define TEST2_HXX
+
+#define MODEL_VERSION 2
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST2_HXX
diff --git a/odb-tests/evolution/version/test3.hxx b/odb-tests/evolution/version/test3.hxx
new file mode 100644
index 0000000..364ee31
--- /dev/null
+++ b/odb-tests/evolution/version/test3.hxx
@@ -0,0 +1,11 @@
+// file : evolution/version/test3.hxx
+// license : GNU GPL v2; see accompanying LICENSE file
+
+#ifndef TEST3_HXX
+#define TEST3_HXX
+
+#define MODEL_VERSION 3
+#include "model.hxx"
+#undef MODEL_VERSION
+
+#endif // TEST3_HXX
diff --git a/odb-tests/evolution/version/testscript b/odb-tests/evolution/version/testscript
new file mode 100644
index 0000000..08290ae
--- /dev/null
+++ b/odb-tests/evolution/version/testscript
@@ -0,0 +1,58 @@
+# file : evolution/version/testscript
+# license : GNU GPL v2; see accompanying LICENSE file
+
+.include ../../database-options.testscript
+.include ../../$db-schema.testscript
+
+test.arguments += $($(db)_options)
+
+: basics
+:
+if! $sqlite
+{
+ ss =; # Schema modification base file names.
+
+ # Drop everything.
+ #
+ for s: $schemas
+ ss =+ $s
+ end;
+
+ # Add base schema.
+ #
+ ss += test3-002-pre test3-002-post;
+
+ # Add migration.
+ #
+ ss += test3-003-pre test3-003-post;
+
+ # Run tests.
+ #
+ for s: $ss
+ f = $out_base/"$s".sql
+
+ if $mysql
+ cat $f | $create_schema_cmd
+ elif $pgsql
+ $create_schema_cmd -f $f
+ elif $oracle
+ $create_schema_cmd "@$f"
+ elif $mssql
+ $create_schema_cmd -i $f
+ end
+
+ if ($s == 'test3-002-post')
+ $* 1
+ elif ($s == 'test3-003-pre')
+ $* 2
+ elif ($s == 'test3-003-post')
+ $* 3
+ end
+ end
+}
+else
+{
+ $* 1 &odb-test.db;
+ $* 2;
+ $* 3
+}